TCP組包問題及處理方法


問題的表述

問題的背景是這樣的:有一個系統,那有後臺服務器,也有移動端的客戶端。當客戶端上線時,服務器會將指定的數據庫的數據發送給客戶端,客戶端解析後呈現。
但是,有一天客戶端開發跟我反映一個奇怪的問題:之前客戶端一上線就收到服務器發來的數據,現在客戶端收到了數據,但是解析出問題了。但是前後端的代碼都沒有改啊,只是要發送的數據是新添加的,並且比之前的測試數據要大。

原因的剖析

根據他的描述,很明顯問題出在了要發送的數據上。由於之前是測試數據,並不會放過大的數據到後臺數據庫,現在放上去的是真實數據,要比測試數據大好多。

於是我就重新拾起了塵封已久的tcp/ip協議的書,也試着在網上尋找答案。最終我知道了我們這個問題叫做tcp協議的組包問題。

什麼叫tcp的組包問題呢?簡單的說就是tcp協議把過大的數據包分成了幾個小的包傳輸,客戶端要把同一組的數據包重新組合成一個完整的數據包。

具體點的解釋:

首先我們要知道MTU(最大傳輸單元)。IP分片在以太網上,由於電氣限制,一幀不能超過1518字節,除去以太網幀頭14字節(mac地址等)和幀尾4字節校驗,不考慮PPPoE協議(讀者自行了解這是什麼鬼)的8個字節,還剩1500字節,這個大小稱爲MTU(最大傳輸單元)。
這1500還包含20字節IP頭,8字節UDP頭或者20字節TCP頭。所以真正的一次發送的數據包,UDP爲1500-28=1472字節,TCP爲1500-40=1460字節。
當然我們要知道tcp是可靠傳輸協議,以字節流的形式傳輸數據的,什麼意思呢?就是他可以允許超過1452字節的數據進行傳輸,因爲他會把它切分成多個包,然後用序號進行排序,從而表明了這幾個包是同一組數據。
但是UDP是不可靠傳輸,它一次性就只能發那麼多了,超過的他就不允許發了,所以他效率高,發送一次就是一個完整的數據,接收方直接拿去解析就可以了。(UDP和TCP還有很多不同的優缺點,讀者感興趣可以自行了解)

所以瞭解了這些之後對於上面描述的問題現象也就不難解釋了,由於我們後臺服務器發送的數據包過大,比如有5000字節,那麼tcp協議就會把它分成1460+1460+1460+620字節,分四個包發送給客戶端,客戶端單單解析一個數據包得到的數據肯定是不完整的,要是傳輸的數據是帶一定的結構的話就會解析出錯了。

發送端處理方法

那麼知道了產生這個問題的原因,又該怎麼解決這個問題呢。我在一個網站上看到有如下回答:鏈接
一 可以每次發送同樣大小的包,過大的包不予發送,過小的包,後面部分用固定的字符’\0’進行填充。
二 將流按字符處理,抽出一個字符做轉義字符(通常Java用’\’來做轉義字符,比如”\n”表示換行)。
三 在發送方發送一個包的時候,先將這個包的長度發送給對方(一般是4個字節表示包長),然後再將包的內容發送過去.接收方先接收4個字節,看看包的長度,然後按照長度來接收包。

於是我採用了最後一種解決辦法,服務器其發送的數據中,前4個字節爲需要發送的數據的字節數,如上所說,這樣的做法其實就是在TCP協議之上又在封裝了一層,只不過很簡單明瞭。

接收端處理方法

因爲TCP會對收到的報文段進行重新排序,然後再交給應用層處理,所以可以用以下的邏輯作爲接收端的處理方法。

算法(裝逼專用詞):

1.設置全局標量flag最爲標識位,初始值爲0;設置全局變量L,初始值爲0。
2.取緩存區數據包,判斷標識位,若爲0,進入步驟3;若爲1,傳遞參數L,進入步驟4。
3.解析數據前4個字節,得到字節數L後與當前數據塊剩下的字節數M比較。若相等,執行步驟5。若L>M,則設置標識位爲1,暫時存儲這M字節數據,傳參數L=L-M,執行步驟2。
4.比較L與當前數據包的大小M,若相等,執行步驟5;若L>M,暫時存儲着M字節數據,傳參數L=L-M,執行步驟2。
5.調用解析函數正常解析,設置標識位爲0。
流程圖如下:

Created with Raphaël 2.1.0開始設置flag=0,L=0取緩存區數據包判斷flag==0?判斷L==M?解析數據,flag=0結束flag=1,L=L-M判斷L==M?yesnoyesnoyesno

總結

事實上,tcp傳輸數據中還存在粘包、分包、半包的問題。發送端的解決辦法還是一樣的,但是接收端的處理邏輯略有不同,這個就等到下一篇博文中再寫吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章