編輯:關於Android編程
我們說過Android中客戶端與服務端通信有兩種方式,HTTP通信和Socket通信,前面我們介紹過HTTP通信了,現在,我們來學習一下Socket通信。學習Socket之前,我們需要先學習一下TCP/IP協議和UDP協議。
我們需要學習的有兩種網絡通信參考模型,分別是TCP/IP參考模型和OSI參考模型,下面我們分別學習一下這兩種參考模型:
TCP/IP參考模型是計算機網絡的祖父ARPANET和其後繼的因特網使用的參考模型。TCP/IP參考模型將協議分為以下的四個層次:
應用層:主要為用戶提供各種服務,例如:Telnet、FTP、DNS等,數據傳輸單位是:數據段
傳輸層:應用層實體提供端到端的通信功能,保證了數據包的順序傳送及數據的完整性。該層定義了兩個主要的協議:傳輸控制協議(TCP)和用戶數據報協議(UDP).數據傳輸單位是:數據包
網際互聯層:主要解決主機到主機的通信問題。它所包含的協議設計數據包在整個網絡上的邏輯傳輸。注重重新賦予主機一個IP地址來完成對主機的尋址,它還負責數據包在多種網絡中的路由。該層有三個主要協議:網際協議(IP)、互聯網組管理協議(IGMP)和互聯網控制報文協議(ICMP)。數據傳輸單位是:幀
網絡接入層:負責監視數據在主機和網絡之間的交換。事實上,TCP/IP本身並未定義該層的協議,而由參與互連的各網絡使用自己的物理層和數據鏈路層協議,然後與TCP/IP的網絡接入層進行連接。數據傳輸單位是:比特
OSI(Open System Interconnect)開放式系統互聯,是ISO(國際標准化組織)組織在1985年研究的網絡互聯模型。該體系結構標准定義了網絡互連的七層框架(物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層),即ISO開放系統互連參考模型。

第7層應用層:OSI中的最高層。為特定類型的網絡應用提供了訪問OSI環境的手段。應用層確定進程之間通信的性質,以滿足用戶的需要。應用層不僅要提供應用進程所需要的信息交換和遠程操作,而且還要作為應用進程的用戶代理,來完成一些為進行信息交換所必需的功能。它包括:文件傳送訪問和管理FTAM、虛擬終端VT、事務處理TP、遠程數據庫訪問RDA、制造報文規范MMS、目錄服務DS等協議;應用層能與應用程序界面溝通,以達到展示給用戶的目的。 在此常見的協議有:HTTP,HTTPS,FTP,TELNET,SSH,SMTP,POP3等。
第6層表示層:主要用於處理兩個通信系統中交換信息的表示方式。為上層用戶解決用戶信息的語法問題。它包括數據格式交換、數據加密與解密、數據壓縮與終端類型的轉換。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrXaNbLju+G7sLLjo7rU2sG9uPa92rXj1q685L2owaK2y8GsvdOho86qtsvPtc2ztcTTptPDs8zQ8tauvOTM4bmpwcu21Luwv9jWxrv61saho7TLt/7O8bD8wKi9qMGiway908rH0tTIq8uruaS7ucrH0tSw68uruaS1xLe9yr29+NDQyejWw6OsvqG53L/J0tTU2rLjNNbQtKbA7curuaS3vcq9IKO7u+G7sLLjudzA7bXHyOu6zdeiz/q5/bPMoaPL/L7fzOW53MDtwb249tPDu6e6zb34s8zWrrzktcS21LuwoaPI57n71NrEs9K7yrG/zNa71MrQ7dK7uPbTw7un1rTQ0NK7z+7M2LaotcSy2df3o6y74buwsuPQrdLpvs274bncwO3V4tCpstnX96OsyOfX6Na5wb249tPDu6fNrMqxuPzQwsr9vt2/4tbQtcTNrNK71+nK/b7doaM8L3A+DQo8cD612jSy47SryuSy46O6Jm1kYXNoO7OjuebK/b7dtd3LzaOtw+bP8sGsvdO78s7eway906Gjzqq74buwsuPTw7unzOG5qdK7uPa2y7W9tsu1xL/Jv7+hos24w/e6zdPFu6+1xMr9vt20q8rkt/7O8bv61saho7D8wKjIq8uruaS78rDry6u5pKGiwfe/2NbGus207c7zu9a4tLf+zvGju7SryuSy47DRz/vPorfWs8nI9LjJuPa31tfpo6yyotTavdPK1bbLttTL/MPHvfjQ0NbY1+mho7K7zay1xLfW1+m/ydLUzai5/bK7zay1xMGsvdO0q8vNtb3W97v6oaPV4tH5vMjE3LvxtcO9z7jftcS0+L/to6zT1rK707DP7Lvhu7Cy46Gj1Nq9qMGiway908qxtKvK5LLjv8nS1Mfrx/O3/s7x1srBv6OsuMO3/s7x1srBv9a4tqi/yb3Tyty1xM7zwuvCyqGi0dOz2cG/oaKwssir0NS1yLLOyv2jrLu5v8nS1Mq1z9a7+dPatsu1vbbLtcTB98G/v9jWxrmmxNyhozwvcD4NCjxwPrXaM7LjzfjC57Ljo7qxvrLjzai5/dGw1rfAtL2owaLBvbj2vdq149auvOS1xMGsvdOjrM6q1LS2y7XE1MvK5LLjy83AtLXEt9bX6aOs0aHU8brPysq1xMK308m6zb27u7u92rXjo6zV/ci3zt7O87XYsLTV1bXY1re0q8vNuPjEv7XEtsu1xNTLyuSy46Gjy/yw/MCozai5/bulwazN+MLnwLTCt9PJus3W0LzMyv2+3SCju7P9wcvRodTxwrfTydauzeKjrM34wuey47u5uLrU8L2owaK6zc6su6TBrL3To6y/2NbGzfjC58nPtcTTtcj70tS8sNTasdjSqrXEyrG68sn6s8m8xrfR0MXPoqGjPC9wPg0KPHA+tdoysuPK/b7dwbTCt7Ljo7rU2rTLsuO9q8r9vt231taho6yyorSmwO3B97/Y1saho8bBsc7O78DtsuOjrM6qzfjC57LjzOG5qdK7uPbK/b7dwbTCt7XEway906Os1NrSu8z109C/ycTcs/ay7rTttcTO78Dtway908nPo6y9+NDQvLi69c7esu607bXEyv2+3bSryuSjqLLutO2/2NbGo6mho7G+suPWuLaozdjGy73hubmyoszhuanTsrz+0bDWt6Gjs6PTw8nosbjT0M34v6ihos34x8Whor27u7u7+qO7PC9wPg0KPHA+tdoxsuPO78DtsuOjurSm09pPU0myzr+8xKPQzbXE1+6117LjoaPO78DtsuO1xNb30qq5psTcysfA+9PDzu/A7bSryuS96dbKzqrK/b7dwbTCt7LjzOG5qc7vwO3BrL3To6zS1LHjzbjD97XEtKvLzbHIzNjB96Gjs6PTw8nosbjT0KOouPfW1s7vwO3J6LG4o6m8r8/fxvehotbQvMzG96GitffWxr3itffG96GizfjP36Giy6u9ys/foaLNrNbhtefAwqGjPC9wPg0KPHA+yv2+3beiy83KsaOstNO12sbfsuO0q7W9tdrSu7Ljo6y908rVyv2+3dTyz+C3tKGjyc/I/bLj19yzxtOm08Oy46Os08PAtL/Y1sbI7bz+t73D5qGjz8LLxLLj19yzxsr9vt3B97Ljo6zTw8C0udzA7dOyvP6ho7P9wcvO78DtsuPWrs3ixuTL+7LjtrzKx9PDyO28/sq1z9a1xKGjyv2+3dTat6LWwcr9vt3B97LjtcTKsbryvauxu7Lwt9aho9TatKvK5LLjtcTK/b7dvdC2zqOszfjC57LjvdCw/KOsyv2+3cG0wrey473Q1qGjrM7vwO2y473QscjM2MH3o6zV4tH5tcS90LeovdBQRFWjqNCt0unK/b7dtaXUqqOpPC9wPg0KPGg0IGlkPQ=="兩種參考模型比較">兩種參考模型比較
兩種參考模型比較圖

共同點
(1)OSI參考模型和TCP/IP參考模型都采用了層次結構的概念。
(2)都能夠提供面向連接和無連接兩種通信服務機制。
不同點
(1)OSI采用的七層模型,而TCP/IP是四層結構。
(2)TCP/IP參考模型的網絡接口層實際上並沒有真正的定義,只是一些概念性的描述。而OSI參考模型不僅分了兩層,而且每一層的功能都很詳盡,甚至在數據鏈路層又分出一個介質訪問子層,專門解決局域網的共享介質問題。
(3)OSI模型是在協議開發前設計的,具有通用性。TCP/IP是先有協議集然後建立模型,不適用於非TCP/IP網絡。
(4)OSI參考模型與TCP/IP參考模型的傳輸層功能基本相似,都是負責為用戶提供真正的端對端的通信服務,也對高層屏蔽了底層網絡的實現細節。所不同的是TCP/IP參考模型的傳輸層是建立在網絡互聯層基礎之上的,而網絡互聯層只提供無連接的網絡服務,所以面向連接的功能完全在TCP協議中實現,當然TCP/IP的傳輸層還提供無連接的服務,如UDP;相反OSI參考模型的傳輸層是建立在網絡層基礎之上的,網絡層既提供面向連接的服務,又提供無連接的服務,但傳輸層只提供面向連接的服務。
(5)OSI參考模型的抽象能力高,適合與描述各種網絡;而TCP/IP是先有了協議,才制定TCP/IP模型的。
(6)OSI參考模型的概念劃分清晰,但過於復雜;而TCP/IP參考模型在服務、接口和協議的 區別上不清楚,功能描述和實現細節混在一起。
(7)TCP/IP參考模型的網絡接口層並不是真正的一層;OSI參考模型的缺點是層次過多,劃分意義不大但增加了復雜性。
(8)OSI參考模型雖然被看好,由於沒把握好時機,技術不成熟,實現困難;相反,TCP/IP參考模型雖然有許多不盡人意的地方,但還是比較成功的。
TCP/IP(transmission Control Protocol/Internet Protocol)傳輸控制協議/互聯網絡協議,是一種網絡通信協議,它規范了網絡上的所有通信設備,尤其是一個主機與另一個主機之間的數據往來格式以及傳送方式。舉個簡單的例子就是:TCP和IP就像兩個信封,我們把要傳輸的數據分割開,放入到TCP信封裡面,TCP信封裡面會記錄有分段號;然後將TCP信封裝入IP這個大信封裡面,傳輸到網絡上。在接收端,一個TCP軟件包收集信封,抽出數據,按發送前的順序還原,並加以校驗,若發現差錯,TCP將會要求重發。
TCP/IP網絡連接需要經過三次握手,斷開連接需要三次揮手,在這裡就不在做很詳細的描述了,推薦一篇博文:TCP/IP三次握手與四次揮手 ,這裡已經很詳細的描述三次握手和四次揮手的過程,建議大家一定要閱讀並理解,面試經常會被問到。關於TCP/IP協議就簡單介紹到這裡,我們做應用開發不是網絡工程師,不需要做很深入的學習。
前面我們介紹了TCP/IP協議,接著我們介紹一下UDP協議。UDP(User Datagram Protocol)用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務,IETF RFC 768是UDP的正式規范。UDP在IP報文的協議號是17。
UDP協議全稱是用戶數據報協議 ,在網絡中它與TCP協議一樣用於處理數據包,是一種無連接的協議。在OSI模型中,在第四層——傳輸層,處於IP協議的上一層。UDP有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之後,是無法得知其是否安全完整到達的。UDP用來支持那些需要在計算機之間傳輸數據的網絡應用。包括網絡視頻會議系統在內的眾多的客戶/服務器模式的網絡應用都需要使用UDP協議。UDP協議從問世至今已經被使用了很多年,雖然其最初的光彩已經被一些類似協議所掩蓋,但是即使是在今天UDP仍然不失為一項非常實用和可行的網絡傳輸層協議。
與所熟知的TCP(傳輸控制協議)協議一樣,UDP協議直接位於IP(網際協議)協議的頂層。根據OSI(開放系統互連)參考模型,UDP和TCP都屬於傳輸層協議。UDP協議的主要作用是將網絡數據流量壓縮成數據包的形式。一個典型的數據包就是一個二進制數據的傳輸單位。每一個數據包的前8個字節用來包含報頭信息,剩余字節則用來包含具體的傳輸數據。
前面介紹的都是概念性的東西,非常枯燥,下面正式開始學習基於TCP的Socket通信。
什麼是Socket?稱作”套接字”,用於描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket通常用來實現客戶方和服務方的連接。Socket是TCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號唯一確定。
Socket通信模型

Server端Listen(監聽)某個端口是否有連接請求,Client端向Server 端發出Connect(連接)請求,Server端向Client端發回Accept(接受)消息。一個連接就建立起來了。Server端和Client 端都可以通過Send,Write等方法與對方通信。
對於一個功能齊全的Socket,都要包含以下基本結構,其工作過程包含以下四個基本的步驟:
1、創建ServerSocket和Socket
2、打開連接到的Socket的輸入/輸出流
3、按照協議對Socket進行讀/寫操作
4、關閉輸入輸出流,以及Socket
下面我們通過Socket搭建一個簡單聊天室來體會一下Socket通信:
首先是服務端的編寫,服務端編寫步驟:
創建ServerSocket對象,綁定監聽的端口
通過調用accept()方法監聽客戶端的請求
建立連接之後,通過輸入流讀取客戶端的請求信息
獲取輸出流向客戶端輸出信息
關閉連接
客戶端的編寫步驟:
創建Socket對象,指明需要連接的服務器的地址和端號
連接建立後,通過輸出流向服務器發送請求信息
通過輸出流獲取服務器響應的信息
關閉連接,釋放相關資源
具體實例代碼:
服務端:
package com.example.socket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Devin on 2016/7/28.
*/
public class SocketServer {
public static boolean isServerStart = true;
private ServerSocket serverSocket;
private static int LISTENER_PART = 10010;
public List socketList = new ArrayList<>();
private ExecutorService executorService;
public void startServer() {
try {
serverSocket = new ServerSocket(LISTENER_PART);
executorService = Executors.newCachedThreadPool();
InetAddress address = InetAddress.getLocalHost();
System.out.println("服務器的IP地址是:" + address.getHostAddress() + ",監聽的端口號是:" + LISTENER_PART);
System.out.println("--------------服務器啟動,等待連接-----------------");
Socket socket = null;
while (isServerStart) {
System.out.println("有用戶登錄進來了");
socket = serverSocket.accept();
socketList.add(socket);
executorService.execute(new ServerThread(socket));
}
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SocketServer socketServer = new SocketServer();
socketServer.startServer();
}
}
客戶端:
package com.example.socketdemo;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.example.socketdemo.activity.FriendsActivity;
import com.example.socketdemo.comm.Constans;
import com.example.socketdemo.comm.ServerConn;
import com.example.socketdemo.comm.ToastUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
public class MainActivity extends AppCompatActivity {
private EditText et_user_id;
private EditText et_user_name;
private Button btn_user_login;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
String isSuccess = bundle.getString("isSuccess");
int userID = bundle.getInt("userID");
if (isSuccess.equals("success")) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, FriendsActivity.class);
intent.putExtra("userID", userID);
startActivity(intent);
} else {
ToastUtils.showToast(MainActivity.this, "登錄失敗");
return;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_user_id = (EditText) findViewById(R.id.et_user_id);
et_user_name = (EditText) findViewById(R.id.et_user_name);
btn_user_login = (Button) findViewById(R.id.btn_user_login);
btn_user_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String userID = et_user_id.getText().toString().trim();
if (userID != null && !userID.equals("")) {
login(Integer.parseInt(userID));
} else {
ToastUtils.showToast(MainActivity.this, "請輸入用戶ID");
return;
}
}
});
}
private void login(int userID) {
final int id = userID;
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(Constans.SERVER_ADDRESS, Constans.SERVER_PORT), 5000);
InetAddress address = InetAddress.getLocalHost();
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println(Constans.TAG_LOGIN + ":" + id + ":" + address.getHostAddress());
writer.flush();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String isSuccess = bufferedReader.readLine();
ServerConn.mSocket = socket;
Message message = mHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString("isSuccess", isSuccess);
bundle.putInt("userID", id);
message.setData(bundle);
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
package com.example.socketdemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.example.socketdemo.R;
import com.example.socketdemo.comm.Constans;
import com.example.socketdemo.comm.OnItemClickListener;
import com.example.socketdemo.domain.UserInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Devin on 2016/8/1.
*/
public class FriendsActivity extends AppCompatActivity {
private RecyclerView rv_friends;
private FriendsAdapter mAdapter;
private List mUserInfos;
private int userID;
private FriendHandler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_friends);
userID = getIntent().getIntExtra("userID", 0);
mHandler = new FriendHandler();
rv_friends = (RecyclerView) findViewById(R.id.rv_friends);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
rv_friends.setLayoutManager(linearLayoutManager);
rv_friends.setItemAnimator(new DefaultItemAnimator());
mUserInfos = new ArrayList<>();
initData(userID);
mAdapter = new FriendsAdapter(this, mUserInfos);
rv_friends.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
UserInfo userInfo = mUserInfos.get(position);
Intent intent = new Intent(FriendsActivity.this, ChatActivity.class);
intent.putExtra("sendID", userID);
intent.putExtra("receiveID", userInfo.getUserID());
intent.putExtra("userName", userInfo.getUserName());
startActivity(intent);
}
});
}
private void initData(final int userID) {
new Thread(new Runnable() {
@Override
public void run() {
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress(Constans.SERVER_ADDRESS, Constans.SERVER_PORT), 5000);
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println(Constans.TAG_FRIENDS + ":" + userID);
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String result = reader.readLine();
System.out.println("-----result---->>"+result);
Bundle bundle = new Bundle();
bundle.putString("result", result);
Message message = mHandler.obtainMessage();
message.setData(bundle);
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private class FriendHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
String result = bundle.getString("result");
List infos = new ArrayList<>();
try {
JSONArray jsonArray = new JSONArray(result);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
UserInfo userInfo = new UserInfo();
userInfo.setUserID(jsonObject.getInt("userID"));
userInfo.setUserIcon(jsonObject.getInt("userIcon"));
userInfo.setUserName(jsonObject.getString("userName"));
userInfo.setUserSign(jsonObject.getString("userSign"));
userInfo.setUserState(jsonObject.getInt("userState"));
infos.add(userInfo);
}
} catch (JSONException e) {
e.printStackTrace();
}
mAdapter.addList(infos);
}
}
}
實現效果圖

這樣可以實現簡單的Socket通信,當然只是很簡單的應用。
Android中Activity的生命周期探討
1、完整生命周期上圖是Android Activity的生命周期圖,其中Resumed、Paused、Stopped狀態是靜態的,這三個狀態下的Activity存在時間較
Android內存分析工具
Android的一些內存知識垃圾回收(GC)垃圾回收包含兩個過程:判定階段,也就是判斷哪些對象可以被回收, 收集階段,是指具體的回收策略。判定階段主要有兩種方式引用計數,
android應用開發之spinner控件的簡單使用
Android的控件有很多種,其中就有一個Spinner的控件,這個控件其實就是一個下拉顯示列表。Spinner是位於 android.widget包下的,每
Android 自定義本地圖片加載庫
總結一下微信的本地圖片加載有以下幾個特點,也是提高用戶體驗的關鍵點1、縮略圖挨個加載,一個一個加載完畢,直到屏幕所有縮略圖都加載完成2、不等當前屏的所有縮略圖加載完,迅速