目錄
如何使用?
1. 示例代碼(基于Netty)
2. 關鍵參數解釋
3. 協議格式示例
4. 常見配置場景
場景1:長度字段包含自身
場景2:長度字段在消息中間
5. 注意事項
舉個例子
完整示例:客戶端與服務端交互流程
1. 服務端代碼(含響應)
2. 客戶端代碼(含編碼器)
3. 執行流程說明
4. 網絡包結構示意圖
5. 關鍵點總結
如何使用?
以下是使用 LengthFieldBasedFrameDecoder
解決 TCP 粘包/拆包問題的 完整代碼示例 和 關鍵解釋:
1. 示例代碼(基于Netty)
// Server端代碼示例
public class NettyServer {public static void main(String[] args) {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {// 關鍵:添加 LengthFieldBasedFrameDecoderch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, // maxFrameLength(最大幀長度)0, // lengthFieldOffset(長度字段偏移量)4, // lengthFieldLength(長度字段占4字節)0, // lengthAdjustment(長度調整值)4 // initialBytesToStrip(跳過前4字節,因為長度字段已解析)));// 將ByteBuf轉為String(按需替換為實際解碼器)ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));// 自定義業務處理器ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) {System.out.println("Received message: " + msg);}});}});ChannelFuture future = bootstrap.bind(8080).sync();future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
2. 關鍵參數解釋
LengthFieldBasedFrameDecoder
的構造函數參數如下:
參數 | 說明 |
| 允許的最大幀長度(防止內存溢出) |
| 長度字段的起始偏移量(通常為0) |
| 長度字段占用的字節數(例如4字節表示int) |
| 長度字段值后的內容長度調整(若長度字段包含自身長度,需調整) |
| 解析后跳過的字節數(例如跳過長度字段本身) |
3. 協議格式示例
假設自定義協議格式如下(長度字段在前):
+--------+----------------+
| Length | Actual Data |
| 4字節 | (變長內容) |
+--------+----------------+
4. 常見配置場景
場景1:長度字段包含自身
// 長度字段包含自身(如總長度= Length字段長度 + 數據長度)
new LengthFieldBasedFrameDecoder(1024, 0, 4, -4, 0);
// lengthAdjustment = -4(扣除長度字段自身占用的4字節)
場景2:長度字段在消息中間
// 消息格式:[Header][Length][Data]
new LengthFieldBasedFrameDecoder(1024, 2, 4, 0, 6);
// lengthFieldOffset=2(跳過Header的2字節)
// initialBytesToStrip=6(跳過Header+Length字段)
5. 注意事項
- 參數匹配協議:必須與協議中長度字段的位置和計算方式一致。
- 編解碼順序:
LengthFieldBasedFrameDecoder
需作為第一個解碼器添加到Pipeline。 - 異常處理:建議配合
ExceptionHandler
處理解碼失敗的情況。
通過這種方式,Netty 會自動根據長度字段切分完整的數據包,徹底解決粘包/拆包問題。
舉個例子
完整示例:客戶端與服務端交互流程
1. 服務端代碼(含響應)
public class NettyServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {// 添加長度字段解碼器ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));// 字符串解碼器ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));// 業務處理器(返回響應)ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) {System.out.println("[Server] Received: " + msg);// 返回響應(添加長度前綴)ctx.writeAndFlush("ACK: " + msg);}});}});ChannelFuture future = bootstrap.bind(8080).sync();future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
2. 客戶端代碼(含編碼器)
public class NettyClient {public static void main(String[] args) throws InterruptedException {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {// 添加編碼器(為消息添加長度前綴)ch.pipeline().addLast(new MessageToByteEncoder<String>() {@Overrideprotected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {byte[] bytes = msg.getBytes(CharsetUtil.UTF_8);out.writeInt(bytes.length); // 寫入4字節長度字段out.writeBytes(bytes); // 寫入實際數據}});// 響應解碼器(與服務端解碼器對稱)ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));// 業務處理器(打印響應)ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) {System.out.println("[Client] Received: " + msg);}});}});ChannelFuture future = bootstrap.connect("localhost", 8080).sync();// 發送兩條測試消息(自動處理粘包)future.channel().writeAndFlush("Hello Netty");future.channel().writeAndFlush("Test Message");future.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}
}
3. 執行流程說明
- 客戶端發送消息:
-
- 編碼器將字符串轉換為
長度字段(4字節) + 實際數據
的二進制格式。 - 示例消息
"Hello Netty"
的傳輸格式:
- 編碼器將字符串轉換為
+----------+-----------------+
| 0x00000B | "Hello Netty" | // 0x00000B = 11字節(字符串長度)
+----------+-----------------+
- 服務端解析消息:
-
LengthFieldBasedFrameDecoder
根據長度字段切分完整數據包。StringDecoder
將二進制數據轉為字符串,業務處理器打印并返回響應。
- 客戶端接收響應:
-
- 服務端返回的
"ACK: Hello Netty"
同樣通過長度字段編碼。 - 客戶端解碼器解析后打印響應信息。
- 服務端返回的
4. 網絡包結構示意圖
客戶端發送:
[Length=11][Data="Hello Netty"][Length=12][Data="Test Message"]服務端接收:
[Length=11][Data="Hello Netty"] → 完整解析為獨立消息
[Length=12][Data="Test Message"] → 完整解析為獨立消息服務端響應:
[Length=16][Data="ACK: Hello Netty"]
[Length=17][Data="ACK: Test Message"]
5. 關鍵點總結
- 編碼對稱性:客戶端和服務端的編解碼器需匹配(長度字段位置一致)。
- 自動分包:
LengthFieldBasedFrameDecoder
自動處理TCP流中的粘包/拆包。 - 性能保障:基于長度字段的解析效率極高,適合高頻數據傳輸場景。
運行示例后,你將在控制臺看到完整的請求-響應日志,驗證粘包問題的解決效果。