网络通信框架Netty的TCP粘包/拆包解决方案

前言针对TCP底层网络通信设计时,当在接收或者发送数据消息时,都需要考虑TCP粘包或者拆包的问题。即可以认为TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,因此一个完整的数据包可能被被TCP拆分成多个包进行发送,也可能把若干个小的数据包封装成一个大的包发送,从而导致数据接收的不完整问题,这就是TCP粘包和拆包问题。

问题说明TCP粘包拆包问题图解


如图1,D1,D2两个数据包,服务端分两次接收到数据包,没有发送粘包和拆包;

图2中D1、D2连在一起,服务端一起接收到数据包,发送了粘包

图3中发生了拆包,服务端分两次接收到数据包,第一次读取到D1数据包的一部分D1-1,第二次读取到数据包D1的第二部分D1-2和数据包D2

解决策略TCP以流的方式进行数据传输,上层的应用协议对消息进行区分,经常采用下面几种方式

1.消息长度固定:累计读取到长度总长为定长Len的报文后,就可以认为读取到一个完整的消息,从而可以将计数器置位进行下一个数据报的读取;

2.消息结束符的设置,如将回车换行符或特殊的分隔符作为消息结束符

3.在消息头中定义消息长度字段来标识消息的总长度

针对以上应用做了统一的抽象,netty提供了工具类来解决对应的问题。使用者不需要对自己读取的报文进行人工解码,也不需要考虑TCP的粘包和拆包问题。

下面给出相应的代码示例,如基于消息长度字段的定长消息解码器

 系统在未加入该解码器时:

Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,3000);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
//服务端返回应答消息时,该方法被调用
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
     //消息处理线程
}
}

以上代码可以看到,在代码中并没有考虑读半包问题。这在功能测试时往往没有问题,但当系统请求压力过大或者发送大报文时,就可能出现粘包/拆包的问题,从而导致消息解码出错,程序就不能达到我们预期的效果。

系统在加入该解码器后:

Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,3000);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(2000,0,4,-4,0));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
//服务端返回应答消息时,该方法被调用
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
// Logger.info("client channelRead");
executor.execute(new ReadResponseProcessor((ByteBuf)msg));//reveive datas
}
}

由代码中可以看到,我们把支持基于长度解码的handler加入ChannelPipeline中,当它依次遍历ByteBuf中的可读字节时,根据长度判断是否已经读取完该条完整消息,因此不会出现因为数据包拆包或粘包导致的一系列问题。

所用解码器类字段释义:

public LengthFieldBasedFrameDecoder(int maxFrameLength,int lengthFieldOffset,int lengthFieldLength,int lengthAdjustment,int initialBytesToStrip)
参数:
maxFrameLength 这个定义最大帧的长度
lengthFieldOffset 长度属性的起始指针(偏移量)
lengthFieldLength 长度属性的长度,即存放数据包长度的变量的的字节所占的长度
lengthAdjustment 这个是一个长度调节值,例如当总长包含头部信息的时候,这个可以是个负数,就比较好实现了
initialBytesToStrip 这个属性也比较好理解,就是解码后的数据包需要跳过的头部信息的字节数

总结】系统在数据消息处理handler之前加入基于长度字段的解码器后,在读取服务端的数据包时发现拆包粘包问题明显改善,基本可以避免消息数据读取越界或不完整的问题。

附录

LengthFieldBasedFrameDecoder 详细API:

https://netty.io/4.0/api/io/netty/handler/codec/LengthFieldBasedFrameDecoder.html#LengthFieldBasedFrameDecoder(int,%20int,%20int)

几类常用解决拆包、粘包问题编(解)码器:

http://blog.csdn.net/zhaowen25/article/details/41122501


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值