TCP/IP和Socket開發經驗分享


當前與網絡相關的業務主要是基於tcp/ip或http,熟悉j2ee的同學一定會對http場景下的開發比較了解。但是,精通tcp/ip以及如何構建一個直接基於tcp/ip層通訊的知識卻不太多見。恰巧,最近一年來我參與了一些基於tcp/ip應用的開發工作。總算有所收獲,今天在博客中做些分享,希望對有興趣的同學有所幫助。

比較常見的4層網絡模型(圖)如下:

基於應用層的開發難度是相對比較低的,因為絕大部分與連接和數據傳輸、校驗相關的事情已經交給(系統)來完成,使得開發人員只需要專注於業務即可。這種分層的技術結構是非常高級和有效的。基於應用層的開發雖然方便,但是當我們需要在功能上實現某些特殊需求的時候,就難免有些掣肘。例如,我們需要從一些傳感器上采集數據或希望他們能夠主動將數據上送,並在經過了中心系統處理后推送到其它響應裝置。這樣的需求使用http來開發,反而增大了難度。

操作系統實際已經為我們提供了一種基於傳輸層的通訊方式:套接字(socket)。使用套接字可以讓我們自由定義通訊協議並選擇合適的連接方式。

利用socket實現網絡通信分為服務端和客戶端,服務端綁定端口並主動監聽連接,客戶端需要向服務端發起連接。建立一次tcp連接需要進過“三次”握手:

第一次握手:客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;

第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

三次握手被抽象成socket連接,這個過程服務端和客戶端會分別生成一個socket並通過在這個套接字上的連接收發數據。那么問題產生了,假如我們知道服務端對8081端口進行監聽,客戶端會隨機打開一個高位端口進行連接。連接建立后,服務端是在哪個端口上監聽數據的呢?答案是8081端口,服務端會根據端口上數據的源地址和端口判斷從而將數據分發到正確的應用上去。

理解這一點其實很重要,如果此時通信的雙方沒有任何數據交換,socket也無法判斷連接是否被斷開。任意一方必須首先通知socket斷開連接,整個通信過程才算結束。如果中間網絡中斷,連接會一直處於等待狀態。

利用socket編程的另一個難點是,由於通信的雙方完全對等任何一方都可以主動發送數據,如何實現在http應用中常見的請求/應答會比較麻煩。為此我專門查閱了http1.0和http1.1的相關資料,基本的解決方案總結如下:

  1. 客戶端等待:客戶端發送請求后,都需要進行堵塞並直到接收到應答或超時為止。這個是http1.0的協議規范,整個數據的交互方式是串行的。
  2. 服務端等待:串行的運行方式實際上浪費了大量的系統運算時間,使得網絡通訊很容易成為整個系統的瓶頸。於是http1.1協議做了更改,客戶端只要准備好請求就可以直接發送,服務端可能會一次性接收到多條請求,但是只能按照請求的順序依次應答。

服務器的運算能力通常都比客戶端強,第二種解決方案能更加有效的利用網絡。但是,如果有一條請求需要請求占用服務端大量的運算時間,后續應答都會被堵塞,因此在某些情況下也會引發比較嚴重的問題。

為了解決這個問題,我借鑒了spring kafka在實現消息交互的時候提供的一種解決思路:為每一條請求指定一個ID,經過服務端處理后的應答都需要帶上這個ID。這樣在回復給客戶端的時候,客戶端就可以根據這條ID值來調用不同的回調處理業務。

與tcp/ip開發的總結,大致如此。后面,我還會分享一些基於技術的實際項目,如果你對這些問題有興趣,也歡迎給我留言討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM