1.粘包和拆包
粘包和拆包是TCP網絡編程中不可避免的,無論是服務端還是客戶端,當我們讀取或者發送消息
的時候,都需要考慮TCP底層的粘包/拆包機制。
TCP是個“流”協議,所謂流,就是沒有界限的一串數據。TCP底層并不了解上層業務數據的具體含義,它會根據TCP緩沖區的實際情況進行包的劃分,所以在業務上認為,一個完整的包可能會被TCP拆分成多個包進行發送,也有可能把多個小的包封裝成一個大的數據包發送,這就是所謂的TCP粘包和拆包
問題
假設客戶端分別發送了兩個數據包D1和D2給服務端,由于服務端一次讀取到的字節數
是不確定的,故可能存在以下4種情況。
TCP粘包和拆包產生的原因:
數據從發送方到接收方需要經過操作系統的緩沖區,而造成粘包和拆包的主要原因就在這個緩沖區上。粘包可以理解為緩沖區數據堆積,導致多個請求數據粘在一起,而拆包可以理解為發送的數據大于緩沖區,進行拆分處理
2. 粘包和拆包的解決方法
(1) 業內解決方案
由于底層的TCP無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個
問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,可以歸納如下。
··消息長度固定,累計讀取到長度和為定長LEN的報文后,就認為讀取到了一個完整的信息
··將換行符作為消息結束符
··將特殊的分隔符作為消息的結束標志,回車換行符就是一種特殊的結束分隔符
··通過在消息頭中定義長度字段來標識消息的總長度
(2)Netty中的粘包和拆包解決方案
Netty提供了4種解碼器來解決,分別如下:
··固定長度的拆包器 FixedLengthFrameDecoder,每個應用層數據包的都拆分成都是固定長度
的大小
··行拆包器 LineBasedFrameDecoder,每個應用層數據包,都以換行符作為分隔符,進行分割
拆分
··分隔符拆包器 DelimiterBasedFrameDecoder,每個應用層數據包,都通過自定義的分隔
符,進行分割拆分
··基于數據包長度的拆包器 LengthFieldBasedFrameDecoder,將應用層數據包的長度,作為
接收端應用層數據包的拆分依據。按照應用層數據包的大小,拆包。這個拆包器,有一個要
求,就是應用層協議中包含數據包的長度
(3)代碼實現
··LineBasedFrameDecoder解碼器
ch.pipeline().addLast(new LineBasedFrameDecoder(2048));
ctx.writeAndFlush(Unpooled.copiedBuffer("你好呀,我是Netty客戶端"+i+"\n", CharsetUtil.UTF_8));
``DelimiterBasedFrameDecoder解碼器
ByteBuf byteBuf =
Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8)); ch.pipeline()
.addLast(new DelimiterBasedFrameDecoder(2048, byteBuf));
ctx.writeAndFlush(Unpooled.copiedBuffer("你好呀,我是Netty客戶端"+i+"$", CharsetUtil.UTF_8));