Socket通訊詳解
前言
Socket一直是android網路程式設計中比較重要的技術,最近專案需要,就自己整理學習了Socket通訊功能的開發。
1. 基礎知識
1.1 計算機網路分層
OSI網路標準分層中,將網路共分為7個層次:
而在實際使用過程中,更多的使用簡化的5層網路結構:
物理層:硬體裝置層,主要規定了網路的一些電器特性,作用是負責傳送0和1的電訊號;
資料鏈路層:單純的0和1沒有任何意義,必須規定解讀方式:多少個電訊號算一組?每個訊號位有何意義?這就是資料鏈路層的工程,主要確定了0/1分組的方式;這個分組方式就是乙太網協議:它將每組訊號分為一個幀(frame),包含幀頭和資料。其中最重要的,這一層中的幀主要根據幀頭中的MAC地址來確定傳送者和接收者,即這一層工作在區域網中;
網路層:
運輸層:通過埠把資料傳到目的主機的目的程序,來實現程序與程序之間的通訊;雖然網路層+資料鏈路層已經可以實現兩臺主機之間的通訊,但是我們還需要區分是兩臺主機中的哪兩個程式在通訊,這就用到了傳輸層中定義的【埠號】概念了;而Unix系統就把主機+埠,叫做“套接字socket”;這一層主要包含兩個協議:UDP、TCP,在下文會介紹;
應用層:規定應用程式的資料格式,這就是最終用於呈現給使用者的資料。
1.2 資料包結構
網路上的資料,在5層網路結構中的傳輸過程如下:
資料從主機1傳送到主機2(不同子網),自上而下通過每一層網路。
資料在通過傳輸層、網路層和資料鏈路層時,會分別被增加該層的頭部標識,最終通過物理層傳輸的資料包格式為:
1.3 C/S結構
定義:即客戶端/伺服器結構,是軟體系統體系結構
作用:充分利用兩端硬體環境的優勢,將任務合理分配到Client端和Server端來實現,降低了系統的通訊開銷。
Socket正是使用這種結構建立連線的,一個套接字接客戶端,一個套接字接伺服器。
從1.1我們知道,Socket是基於TCP和UDP協議的,接下來介紹這兩個協議。
1.4 TCP協議
- 定義:Transmission Control Protocol,即傳輸控制協議,是一種傳輸層通訊協議
基於TCP的應用層協議有FTP、Telnet、SMTP、HTTP、POP3與DNS。
特點:面向連線、面向位元組流、全雙工通訊、可靠
面向連線:指的是要使用TCP傳輸資料,必須先建立TCP連線,傳輸完成後釋放連線,就像打電話一樣必須先撥號建立一條連線,打完後掛機釋放連線。
全雙工通訊:即一旦建立了TCP連線,通訊雙方可以在任何時候都能傳送資料。
可靠的:指的是通過TCP連線傳送的資料,無差錯,不丟失,不重複,並且按序到達。
面向位元組流:流,指的是流入到程序或從程序流出的字元序列。簡單來說,雖然有時候要傳輸的資料流太大,TCP報文長度有限制,不能一次傳輸完,要把它分為好幾個資料塊,但是由於可靠性保證,接收方可以按順序接收資料塊然後重新組成分塊之前的資料流,所以TCP看起來就像直接互相傳輸位元組流一樣,面向位元組流。
TCP建立連線
必須進行三次握手:若A要與B進行連線,則必須
- 第一次握手:建立連線。客戶端傳送連線請求報文段,將SYN位置為1,Sequence Number為x;然後,客戶端進入SYN_SEND狀態,等待伺服器的確認。即A傳送資訊給B
- 第二次握手:伺服器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認。即B收到連線資訊後向A返回確認資訊
- 第三次握手:客戶端收到伺服器的(SYN+ACK)報文段,並向伺服器傳送ACK報文段。即A收到確認資訊後再次向B返回確認連線資訊
此時,A告訴自己上層連線建立;B收到連線資訊後告訴上層連線建立。
這樣就完成TCP三次握手 = 一條TCP連線建立完成 = 可以開始傳送資料
- 三次握手期間任何一次未收到對面回覆都要重發。
- 最後一個確認報文段傳送完畢以後,客戶端和伺服器端都進入ESTABLISHED狀態。
為什麼TCP建立連線需要三次握手?
答:防止伺服器端因為接收了早已失效的連線請求報文從而一直等待客戶端請求,從而浪費資源
- “已失效的連線請求報文段”的產生在這樣一種情況下:Client發出的第一個連線請求報文段並沒有丟失,而是在某個網路結點長時間的滯留了,以致延誤到連線釋放以後的某個時間才到達server。
- 這是一個早已失效的報文段。但Server收到此失效的連線請求報文段後,就誤認為是Client再次發出的一個新的連線請求。
- 於是就向Client發出確認報文段,同意建立連線。
- 假設不採用“三次握手”:只要Server發出確認,新的連線就建立了。
- 由於現在Client並沒有發出建立連線的請求,因此不會向Server傳送資料。
- 但Server卻以為新的運輸連線已經建立,並一直等待Client發來資料。>- 這樣,Server的資源就白白浪費掉了。
採用“三次握手”的辦法可以防止上述現象發生:
- Client不會向Server的確認發出確認
- Server由於收不到確認,就知道Client並沒有要求建立連線
所以Server不會等待Client傳送資料,資源就沒有被浪費
TCP釋放連線
TCP釋放連線需要四次揮手過程,現在假設A主動釋放連線:(資料傳輸結束後,通訊的雙方都可釋放連線)- 第一次揮手:A傳送釋放資訊到B;(發出去之後,A->B傳送資料這條路徑就斷了)
第二次揮手:B收到A的釋放資訊之後,回覆確認釋放的資訊:我同意你的釋放連線請求
第三次揮手:B傳送“請求釋放連線“資訊給A
第四次揮手:A收到B傳送的資訊後向B傳送確認釋放資訊:我同意你的釋放連線請求
B收到確認資訊後就會正式關閉連線;
A等待2MSL後依然沒有收到回覆,則證明B端已正常關閉,於是A關閉連線
為什麼TCP釋放連線需要四次揮手?
為了保證雙方都能通知對方“需要釋放連線”,即在釋放連線後都無法接收或傳送訊息給對方
- 需要明確的是:TCP是全雙工模式,這意味著是雙向都可以傳送、接收的
- 釋放連線的定義是:雙方都無法接收或傳送訊息給對方,是雙向的
- 當主機1發出“釋放連線請求”(FIN報文段)時,只是表示主機1已經沒有資料要傳送 / 資料已經全部發送完畢;
但是,這個時候主機1還是可以接受來自主機2的資料。
- 當主機2返回“確認釋放連線”資訊(ACK報文段)時,表示它已經知道主機1沒有資料傳送了
但此時主機2還是可以傳送資料給主機1 - 當主機2也傳送了FIN報文段時,即告訴主機1我也沒有資料要傳送了
此時,主機1和2已經無法進行通訊:主機1無法傳送資料給主機2,主機2也無法傳送資料給主機1,此時,TCP的連線才算釋放
1.5 UDP協議
定義:User Datagram Protocol,即使用者資料報協議,是一種傳輸層通訊協議。
基於UDP的應用層協議有TFTP、SNMP與DNS。
特點:無連線的、不可靠的、面向報文、沒有擁塞控制
無連線的:和TCP要建立連線不同,UDP傳輸資料不需要建立連線,就像寫信,在信封寫上收信人名稱、地址就可以交給郵局傳送了,至於能不能送到,就要看郵局的送信能力和送信過程的困難程度了。
不可靠的:因為UDP發出去的資料包發出去就不管了,不管它會不會到達,所以很可能會出現丟包現象,使傳輸的資料出錯。
面向報文:資料報文,就相當於一個數據包,應用層交給UDP多大的資料包,UDP就照樣傳送,不會像TCP那樣拆分。
- 沒有擁塞控制:擁塞,是指到達通訊子網中某一部分的分組數量過多,使得該部分網路來不及處理,以致引起這部分乃至整個網路效能下降的現象,嚴重時甚至會導致網路通訊業務陷入停頓,即出現死鎖現象,就像交通堵塞一樣。TCP建立連線後如果傳送的資料因為通道質量的原因不能到達目的地,它會不斷重發,有可能導致越來越塞,所以需要一個複雜的原理來控制擁塞。而UDP就沒有這個煩惱,發出去就不管了。
應用場景
很多的實時應用(如IP電話、實時視訊會議、某些多人同時線上遊戲等)要求源主機以很定的速率傳送資料,並且允許在網路發生擁塞時候丟失一些資料,但是要求不能有太大的延時,UDP就剛好適合這種要求。所以說,只有不適合的技術,沒有真正沒用的技術。
2. Socket定義與原理
定義:
Socket是一個對 TCP / IP協議進行封裝的程式設計呼叫介面(API)。它是應用層與傳輸層之前的橋樑,socket本質是程式設計介面(API),對TCP/IP的封裝,TCP/IP也要提供可供程式設計師做網路開發所用的介面,這就是Socket程式設計介面。
原理:
Socket
的使用型別主要有兩種:
- 流套接字(
streamsocket
) :基於TCP
協議,採用 流的方式 提供可靠的位元組流服務 - 資料報套接字(
datagramsocket
):基於UDP
協議,採用 資料報文 提供資料打包傳送的服務
具體原理圖如下:
3. Socket與HTTP對比
Socket
屬於傳輸層,因為TCP / IP
協議屬於傳輸層,解決的是資料如何在網路中傳輸的問題HTTP
協議 屬於 應用層,解決的是如何包裝資料
由於二者不屬於同一層面,所以本來是沒有可比性的。但隨著發展,預設的Http裡封裝了下面幾層的使用,所以才會出現Socket
& HTTP
協議的對比:(主要是工作方式的不同):
Http
:採用 請求—響應 方式。- 即建立網路連線後,當 客戶端 向 伺服器 傳送請求後,伺服器端才能向客戶端返回資料。
- 可理解為:是客戶端有需要才進行通訊
Socket
:採用 伺服器主動傳送資料 的方式- 即建立網路連線後,伺服器可主動傳送訊息給客戶端,而不需要由客戶端向伺服器傳送請求
- 可理解為:是伺服器端有需要才進行通訊
4. Socket客戶端DEMO(TCP協議)
4.1 使用步驟
// 1. 建立Socket客戶端並連線伺服器
Socket mSocket = new Socket(url, port);
......
// 2. 獲取Socket輸入/輸出流
OutputStream os = mSocket.getOutputStream();
InputStream is = mSocket.getInputStream();
......
// 3. 寫資料,即傳送資料給伺服器
try {
byte[] temp = mData.poll();
if (null != temp) {
os.write(temp);
os.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
......
// 4. 讀資料,即接收伺服器資料
try {
if (is.read(read) > 0) {
mSocketPresenter.receive(read);
}
} catch (IOException e) {
e.printStackTrace();
}
......
// 5. 斷開 客戶端傳送到伺服器 的連線,即關閉輸出流物件OutputStream
if (null != os) {
os.close();
os = null;
}
// 6. 斷開 客戶端接收伺服器的連線,即關閉輸出流物件OutputStream
if (null != is) {
is.close();
is = null;
}
// 7. 關閉整個Socket連線
if (null != mSocket) {
mSocket.close();
}
4.2 DEMO原始碼
見github程式碼,其中使用MVP架構寫的這個測試工程,裡邊包含了一些其他內容,關於Socket通訊的模組,請檢視SocketConnection.java、SocketActivityView.java以及SocketPresenter.java類,其中SocketConnection.java封裝了Socket通訊的建立、資訊傳送以及斷開。
最終的結果效果圖(使用了一個Socket測試的APP【網路測試】建立Socket服務端)