使用Netty通信時,遇到TCP粘包拆包問題如何解決?答案如此簡單
這篇文章會按照以下步驟進行講解,希望對你有所收獲:
1、什么是TCP粘包拆包2、Netty中粘包問題的問題重現3、Netty中粘包問題的解決方案
OK,在你心中有這么一個基本的脈絡之后就可以開始今天的文章了。本系列所有的文章都會給出完整的代碼,且在電腦上真實運行了一遍,確保無誤。
一、什么是TCP拆包和粘包
我們使用TCP協議在傳輸數據的時候,如果數據塊比較大,就會考慮將其切分。把一個大的數據包進行切割成一個個小的數據包發送。這時候就會遇到拆包和粘包的問題。
比如說在這里客戶端發送了兩個數據包D1和D2到服務端,在傳輸的時候就可能會遇到下列問題:

通過上面這張圖相信你基本上能夠理解了。不過我們在這里還是需要稍微解釋一下:
情況1:D1和D2正常發送,每次發送一個整包。
情況2:D1數據包比較大,D2比較小。第一次發送D1的一部分,第二次發送D1剩下的和D2整包。這叫拆包。
情況2:D1和D2數據包都比較小,一次發送兩個整包,這就叫做粘包。
情況4:D1數據包比較小,D2比較大。第一次發送D1整包和D2一部分,第二次發送D2剩下的。這叫拆包。
情況5:D1和D2數據包都比較大,這時候分開發。
為什么會出現這樣的問題呢?想要解釋清楚,就必須要考慮到計算機網絡的相關知識了,TCP在接受數據的時候,有一個滑動窗口來控制接受數據的大小,這個滑動窗口你就可以理解為一個緩沖區的大小。緩沖區滿了就會把數據發送。數據包的大小是不固定的,有時候比緩沖區大,有時候小。這時候就會出現上面的現象。
下面我們使用代碼來重現這個現象。
二、問題重現
1、前提準備
我們是基于Springboot開發的,因此還是和上一節一樣,首先創建一個Springboot的web工程,添加一下依賴:

如果你沒有使用maven,下載相關jar包,直接導入IDE中即可。
2、服務端代碼開發
步驟一:創建server類
這個server類,在上一篇文章中提到,是一個模板類,直接拿來用即可。

在上面的這個代碼中同樣我們最主要的是關注ServerUAVHandler的實現。
步驟二:Handler的實現

在這個類中,使用channelRead方法來讀取客戶端發送過來的信息。
(1)首先定義了一個counter,用于計算客戶端發送了多少條消息。
(2)在channelRead內部,首先將msg轉化為ByteBuf。
(3)將buf的數據轉化為字節byte
(4)將buf的字節數據轉化為String類型,然后輸出。
(5)使用ctx的writeAndFlush方法,每收到一個客戶端的數據,給對方回復一個A。別忘了還有一個換行符。
在上面的這個代碼中,最主要的就是服務端每收到一條客戶端的信息,就給其回復一條。也就是說客戶端和服務端的消息數量應該是一樣的。
3、客戶端代碼開發
步驟一:創建client類

同樣的代碼的邏輯在上一篇文章中已經說了,我們還是最關注的事件處理類Handler。
步驟二:Handler實現

這個客戶端的Handler看起來有點多,一共有兩個方法,channelActive和channelRead。
(1)channelActive里面使用for循環給服務器發送了100條,我愛你。每次發送還有在末尾添加一個換行符。
(2)channelRead里面接受服務器返回的消息。
按道理來講,客戶端給服務端發送了100條數據,那么服務端也會返回回來100條。我們來驗證一下。

這里輸出的是服務端的信息,從上面的輸出結果你就會發現,其實客戶端的“我愛你”都被黏在了一塊。本來100條但是現在卻只有17條了,這就是發生了粘包現象。
如何來解決呢?下面我們看看。
三、粘包問題解決
解決的思路很簡單,也就是每次發送一個數據包的時候,添加一個標識符,讀的時候一直讀到這個標識符才表示一個完整的數據包。在上面我們添加的是line.separator,也就是換行符“\n”。
1、服務端server類更改。

2、服務端Handler類更改

3、客戶端Client更改

4、客戶端Handler更改

客戶端和服務端改的地方都一樣,不過還是貼了出來,現在我們再運行一波。

看到沒是不是很神奇。我們來分析一下我們都修改了什么。
好像我們就只是在server和client類添加了兩個類,一個是LineBasedFrameDecoder,一個是StringDecoder,其他的都是直接刪除,這兩個類有什么作用呢?
(1)LineBasedFrameDecoder的作用是在讀取數據的時候,一直讀到是否含有換行符“\n”或者是“\r\n”。如果讀到了就表示該結束了。因此就拿到了這一行的數據包。
(2)StringDecoder用于對之前LineBasedFrameDecoder讀取的這一行數據包進行解碼。將對象轉換為字符串。
OK,好像他們倆搭配,干活真不累,現在我們終于可以解決粘包的問題了,但是同時也出現了一個新的問題,那就是如果我們的標識符不是換行符“\n”或者是“\r\n”又該怎么辦呢?幸好Netty同樣為我們提供了幾種其他的解碼器,叫做DelimiterBasedFrameDecoder和FixedLengthFrameDecoder,前面這個可以自動完成以分隔符做結束標志的消息,后面這個可以自動完成對定長消息的解碼。都可以解決粘包拆包問題。






























