netty5.0中有一个非常方便的传输解析器:LengthFieldBasedFrameDecoder。下面是LengthFieldBasedFrameDecoder的构造方法
/** * Creates a new instance. * * @param maxFrameLength * the maximum length of the frame. If the length of the frame is * greater than this value, {@link TooLongFrameException} will be * thrown. * @param lengthFieldOffset * the offset of the length field * @param lengthFieldLength * the length of the length field * @param lengthAdjustment * the compensation value to add to the value of the length field * @param initialBytesToStrip * the number of first bytes to strip out from the decoded frame */ public LengthFieldBasedFrameDecoder( int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) { this( maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, true); }
maxFrameLength:数据包最大允许长度
lengthFieldOffset:长度字段的偏移量
lengthFieldLength:长度字段的长度
lengthAdjustment:补位值
initialBytesToStrip:从解码器中剔除的长度(设置为上面长度字段的长度)
这样看来,只需在netty服务端添加一个解析器就可以解决TCP粘包问题。服务端代码就不全贴上来了,只贴上有变动的部分,不明白的读者请看一下前两篇netty文章。
bootstrap.childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline p = socketChannel.pipeline(); // 定义传输解析器,数据包前加4个字节表示包的总长度 p.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 4, 0, 4)); // 维持心跳 p.addLast(new IdleStateHandler(15, 30, 30, TimeUnit.SECONDS)); p.addLast(new NettyIdleStateHandler()); p.addLast(new NettyServerHandler()); } });
添加解析器后,只需要按照约定往服务器发送数据包时在数据包头部添加一个值来说明后面数据的字节长度即可。
客户端发送数据包时格式如下:
public static ByteBuf newProtocol(String message) throws UnsupportedEncodingException { byte[] req = message.getBytes(Constant.UTF8); ByteBuf pingMessage = Unpooled.buffer(); pingMessage.writeInt(req.length); pingMessage.writeBytes(req); return pingMessage; }
如此TCP粘包/拆包问题即可轻松愉快的解决。