《計算機網(wǎng)絡(luò)》給我們仔仔細細的講了TCP/IP協(xié)議的5層架構(gòu),在理論層面分析了數(shù)據(jù)如何從上而下,變成了二進制信號阿宅,最后通過電纜傳輸?shù)椒?wù)端。而服務(wù)端又從下而上笼蛛,將電信號解析洒放,最后層層封裝成我們需要的數(shù)據(jù)。
而這一切和我們的Java有什么關(guān)系呢滨砍,且看下文往湿。
簡單說一下Socket
對于部分Android程序員來說妖异,直接用Java去和服務(wù)端通信可能是很久遠的事情了,現(xiàn)在來回顧一下代碼:
客戶端代碼
/**
* Socket客戶端
*/
public static void main(String[] args) {
//創(chuàng)建Socket對象
Socket socket=new Socket("localhost",8888);
OutputStream outputStream=socket.getOutputStream();//獲取一個輸出流领追,向服務(wù)端發(fā)送信息
PrintWriter printWriter=new PrintWriter(outputStream);//將輸出流包裝成打印流
printWriter.print("服務(wù)端你好!");
printWriter.flush();
socket.shutdownOutput();//關(guān)閉輸出流
}
客戶端的整個過程非常簡單他膳,清晰
- 建立Socket連接(底層相當(dāng)于建立TCP連接)
- 獲取Socket連接的輸出流(如果鏈接建立失敗,則會拋出異常)
- 向流中寫入數(shù)據(jù)蔓腐,并flush(這代表數(shù)據(jù)發(fā)送到服務(wù)端)
- 關(guān)閉輸入流(流對象在操作系統(tǒng)看來是一種資源矩乐,上層使用完之后必須關(guān)閉)
那么,Socket到底是個什么東西呢回论?
每一種編程語言都要進行網(wǎng)絡(luò)操作散罕,而TCP/IP協(xié)議是計算機體系層面的,他是一個通用的概念傀蓉,所以Java就對其封裝成了一個Socket類欧漱,對于Java開發(fā)者來說,要想進行網(wǎng)絡(luò)通信葬燎,使用Socket對象就可以了误甚。
TCP連接
計算機體系是一個嚴謹?shù)膶蛹壗Y(jié)構(gòu),下層為上層提供接口谱净,上層只需要按照下層提供的接口傳輸數(shù)據(jù)窑邦,就可以和下層通信。比如操作系統(tǒng)壕探,它本身也是一個運行在計算機中的軟件冈钦,他為應(yīng)用程序提供接口,應(yīng)用程序就可以間接調(diào)用操作系統(tǒng)的資源李请。
TCP的全稱是傳輸層控制協(xié)議瞧筛,它的作用是和服務(wù)器之間建立起通信的管道,后續(xù)的所有數(shù)據(jù)都要通過這個管道傳輸?shù)椒?wù)器上导盅,當(dāng)數(shù)據(jù)傳輸完成以后较幌,這個管道(在操作系統(tǒng)看來是一種資源)會被關(guān)閉。
建立管道的過程就是TCP的3次握手
- 客戶端發(fā)送SYN報文給服務(wù)器端白翻,客戶端進入SYN_SEND狀態(tài)乍炉。
- 服務(wù)端收到SYN報文,回應(yīng)一個SYN ACK報文滤馍,服務(wù)端進入SYN_RECV狀態(tài)
- 客戶端收到服務(wù)端的SYN報文恩急,回應(yīng)一個ACK報文,客戶端進入ESTABLISHED狀態(tài)纪蜒;服務(wù)端收到這個ACK報文后,進入ESTABLISHED狀態(tài)此叠。
那么纯续,問題來了随珠,為什么要進行3次握手呢?
明明2次就可以建立連接的猬错,計算機網(wǎng)絡(luò)中的解釋如下:
為了防止已失效的請求報文突然又傳送到了服務(wù)端窗看,因而產(chǎn)生錯誤。
舉個栗子:
什么叫"已失效的請求報文"呢倦炒?
client第一次發(fā)出的請求報文在某個網(wǎng)絡(luò)節(jié)點滯留显沈,以致于該報文到達Server后,TCP連接已經(jīng)被客戶端釋放逢唤。此時拉讯,如果不采用"三次握手",那么服務(wù)端會認為客戶端想和它建立連接鳖藕,就會一直傻傻的等著魔慷,這樣白白浪費了server的資源。
如果采用了"三次握手"著恩,server收到滯留報文后給client發(fā)出確認消息院尔,client不會向server作出回應(yīng),server由于收不到確認喉誊,也就不會建立連接邀摆。
終止連接需要4次揮手
細節(jié)略。
KeepAlive和心跳包
首先明確一點伍茄,在TCP層是沒有“請求”一說的栋盹,TCP是一種通信方式(對應(yīng)的還有UDP)』昧郑”請求“一詞是事務(wù)上的概念贞盯,HTTP協(xié)議是一種事務(wù)協(xié)議,所以我們可以說發(fā)送一個HTTP請求沪饺。
TCP層的KeepAlive
當(dāng)TCP連接建立后躏敢,如果上層協(xié)議一直不發(fā)送數(shù)據(jù),或者隔了很長時間才發(fā)送一次數(shù)據(jù)整葡,TCP層需要自己去解決這個問題件余。
當(dāng)超過一段時間后,TCP自動發(fā)送一個數(shù)據(jù)為空的報文給對方遭居,如果對方回應(yīng)了啼器,說明對方還在線,連接可以繼續(xù)保持俱萍;如果對方?jīng)]有報文返回端壳,并且在重試了多次之后則認為連接丟失。
注意:上述的實現(xiàn)在TCP層完成枪蘑。
HTTP層的Keep-Alive
一個完整的HTTP事物如下:
- 建立連接
- 傳輸數(shù)據(jù)
- 關(guān)閉連接
在展示一個網(wǎng)頁時损谦,可能有很多次請求岖免,圖片,JS,CSS等等,如果每一個HTTP請求都需要一個完整的HTTP事務(wù)來完成照捡,這樣的開銷(建立連接颅湘、關(guān)閉連接)是沒必要的。
開啟了HTTP Keep-Alive之后栗精,能復(fù)用已有的TCP連接闯参。HTTP/1.1之后默認開啟Keep-Alive, 在HTTP的頭域中增加Connection選項。當(dāng)設(shè)置為Connection:keep-alive表示開啟悲立,設(shè)置為Connection:close表示關(guān)閉鹿寨。
App中的長連接
首先,這里的長連接肯定是TCP層面的連接级历。上面已經(jīng)提到過释移,TCP層建立的連接本身就是一個長連接,系統(tǒng)默認的探測時間是2小時寥殖。
對于移動端來說玩讳,他被分配的IP是內(nèi)網(wǎng)IP,它通過NAT發(fā)送數(shù)據(jù)到外網(wǎng)的服務(wù)器嚼贡,NAT內(nèi)部維護了一張映射表熏纯,這個表有一個維持時間,如果一段時間之后移動端沒有再發(fā)送數(shù)據(jù)粤策,那么移動端的IP在這張映射表上就會被抹掉樟澜。那么服務(wù)器也就不可能再推送消息給移動端了。
所以叮盘,移動端必須在一定的間隔時間內(nèi)去發(fā)送一個"心跳包"來維護NAT上的映射表秩贰,這樣NAT才能將服務(wù)端推送的消息轉(zhuǎn)發(fā)給移動端。
長連接具體的實現(xiàn)例子很多柔吼,根據(jù)不同的業(yè)務(wù)場景而不同毒费,但原理是一樣的。
參考文章:
一個運維對KeepAlive的理解