網絡通訊中粘包的處理


在網絡通訊中,不僅僅是TCP通訊,也包括串口通訊中,我們經常會遇到數據包粘連的問題,本文詳細介紹粘包問題產生的原因和解決辦法。

一、粘包定義

TCP 傳輸中,客戶端發送數據,實際是把數據寫入到了 TCP 的緩存中,由於傳輸的過程為數據流,經過TCP傳輸后,多條數據被合並成了一條,這就是數據粘包了。圖示如下:

二、產生原因

其實從上面的定義,我們就可以大概知道產生的原因了。

粘包的主要原因:

  • 發送方每次寫入數據 < 套接字(Socket)緩沖區大小
  • 接收方讀取套接字(Socket)緩沖區數據不夠及時

半包的主要原因:

  • 發送方每次寫入數據 > 套接字(Socket)緩沖區大小
  • 發送的數據大於協議的 MTU (Maximum Transmission Unit,最大傳輸單元),因此必須拆包

其實我們可以換個角度看待問題:

  • 從收發的角度看,便是一個發送可能被多次接收,多個發送可能被一次接收。
  • 從傳輸的角度看,便是一個發送可能占用多個傳輸包,多個發送可能共用一個傳輸包。

根本原因,其實是

TCP 是流式協議,消息無邊界。

(PS : UDP 雖然也可以一次傳輸多個包或者多次傳輸一個包,但每個消息都是有邊界的,因此不會有粘包和半包問題。)

 

三、解決方法

就像上面說的,UDP 之所以不會產生粘包和半包問題,主要是因為消息有邊界,因此,我們也可以采取類似的思路。

1. 改成短連接

將 TCP 連接改成短連接,一個請求一個短連接。這樣的話,建立連接到釋放連接之間的消息即為傳輸的信息,消息也就產生了邊界。

這樣的方法就是十分簡單,不需要在我們的應用中做過多修改。但缺點也就很明顯了,效率低下,TCP 連接和斷開都會涉及三次握手以及四次握手,每個消息都會涉及這些過程,十分浪費性能。

因此,並不推介這種方式。

2. 固定長度

這種方式下,消息邊界也就是固定長度即可。

優點就是實現很簡單,缺點就是空間有極大的浪費,如果傳遞的消息中大部分都比較短,這樣就會有很多空間是浪費的。

因此,這種方式一般也是不推介的。

3. 分隔符

這種方式下,消息邊界也就是分隔符本身。

優點是空間不再浪費,實現也比較簡單。缺點是當內容本身出現分割符時需要轉義,所以無論是發送還是接受,都需要進行整個內容的掃描。

因此,這種方式效率也不是很高,但可以嘗試使用。

4. 專門的 length 字段

這種方式,就有點類似 Http 請求中的 Content-Length,有一個專門的字段存儲消息的長度。作為服務端,接受消息時,先解析固定長度的字段(length字段)獲取消息總長度,然后讀取后續內容。

優點是精確定位用戶數據,內容也不用轉義。缺點是長度理論上有限制,需要提前限制可能的最大長度從而定義長度占用字節數。

因此,十分推介用這種方式。

5. 其他方式

其他方式就各不相同了,比如 JSON 可以看成是使用{}是否成對。這些優缺點就需要大家在各自的場景中進行衡量了。

 

四、舉例

Netty 中的實現:使用了固定長(FixedLengthFrameDecoder)、分隔符(DelimiterBasedFrameDecoder)、專門的length字段(LengthFieldBasedFrameDecoder)三種方案;

CMPP協議中:協議頭有Length字段定義包的長度,接收方按長度進行拆分即可。(主要是接收SubmitResp、Deliver包需要特別注意粘包的處理)

SIGP協議、SMGP協議與CMPP協議類似,協議中都定義了包的長度Length字段。

對於單個數據緩沖區可能接收到半包的問題,可以設計一個滾動的長緩存區負責接收存放數據,這樣多個半包可以重新組合成一個完整包,然后另起解析線程異步對包進行拆解處理。

下面代碼片段顯示了實現:

這里接收數據的線程(發送線程不存在粘連包的問題),只負責接收並往Buffer里插入數據,Buffer里再去按協議包格式長度進行拆包。這里雖然臨時接收緩存為1024個字節,但並不用擔心數據包會截斷,因為所有數據包最終都會組合到一個長緩存區進行拆解。Buffer類的代碼片段:

 


免責聲明!

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



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