「Netty系列」TCP通信过程中遇到粘包拆包解决全过程

周末了,文章继续。在使用TCP协议进行通信时,听到最多的也就是粘包和拆包问题。本文就来看看,如何解决粘包和拆包问题。      

一 TCP的粘包/拆包的问题以及解决

在解决TCP粘包和拆包,我们先看看一种思想。来看看读取一个Int数据的Demo,体会下这种思想。

1.1 ReplayingDecoder

1. 自定义解码器,从ByteBuf读取一个Int。(重点,一定要看懂这段代码)

public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
      if (buf.readableBytes() < 4) {
        return;
      }
      buf.markReaderIndex();//标记下当前读指针。
      int length = buf.readInt();//从ByteBuf读出一个int
      if (buf.readableBytes() < length) {
        buf.resetReaderIndex();//恢复到刚才标记的读指针
        return; 
      }
      out.add(buf.readBytes(length));
    }
}

2. 使用ReplayingDecoder进行优化()

public class IntegerHeaderFrameDecoder extends ReplayingDecoder<Void> {
    protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
       out.add(buf.readBytes(buf.readInt()));
    }
}

 

3. ReplayingDecoder使用说明(重点,要理解的)

a 使⽤了特殊的ByteBuf,叫做ReplayingDecoderByteBuf,扩展了ByteBuf

 

b 重写了ByteBuf的readXxx()等⽅法,会先检查可读字节⻓度,⼀旦检测到不满⾜要求就直接抛出REPLAY(REPLAY继承ERROR)

  

c ReplayingDecoder重写了ByteToMessageDecoder的callDecode()⽅法,捕获Signal并在catch块中重置ByteBuf的readerIndex。

  

d 继续等待数据,直到有了数据后继续读取,这样就可以保证读取到需要读取的数据。

 

e 类定义中的泛型S是⼀个⽤于记录解码状态的状态机枚举类,在state(S s)、checkpoint(S s)等⽅法中会⽤到。在简单解码时也可以⽤java.lang.Void来占位。

 

总结:

ReplayingDecoder是ByteToMessageDecoder的子类,扩展了ByteBuf。从写了readXxx()等⽅法,当前ByteBuf中数据小于代取数据,等待数据满足,才能取数据。就可以省略手动实现这段代码。

 

4. 注意

1 buffer的部分操作(readBytes(ByteBuffer dst)、retain()、release()等⽅法会直接抛出异常)
2 在某些情况下会影响性能(如多次对同⼀段消息解码)

 

继承ReplayingDecoder,错误示例和修改

//这是⼀个错误的例⼦:
//消息中包含了2个integer,代码中decode⽅法会被调⽤两次,此时队列size不等于2,这段代码达不到期望结果。
public class MyDecoder extends ReplayingDecoder<Void> {
    private final Queue<Integer> values = new LinkedList<Integer>();
    @Override
    public void decode(ByteBuf buf, List<Object> out) throws Exception {
        // A message contains 2 integers.
        values.offer(buf.readInt());
        values.offer(buf.readInt());
        assert values.size() == 2;
        out.add(values.poll() + values.poll());
    }
}
//正确的做法:
public class MyDecoder extends ReplayingDecoder<Void> {
    private final Queue<Integer> values = new LinkedList<Integer>();
    @Override
    public void decode(ByteBuf buf, List<Object> out) throws Exception {
        // Revert the state of the variable that might have been changed
        // since the last partial decode.
        values.clear();
        // A message contains 2 integers.
        values.offer(buf.readInt());
        values.offer(buf.readInt());
        // Now we know this assertion will never fail.
        assert values.size() == 2;
        out.add(values.poll() + values.poll());
    }
} 

 

ByteToIntegerDecoder2的实现

public class ByteToIntegerDecoder2 extends ReplayingDecoder<Void> {
    /**
    * @param ctx 上下⽂
    * @param in 输⼊的ByteBuf消息数据
    * @param out 转化后输出的容器
    * @throws Exception
    */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
     out.add(in.readInt()); //读取到int类型数据,放⼊到输出,完成数据类型的转化
    }
}

 

1.2 拆包和粘包问题重现(客户端向服务端发送十条数据)

1. 客户端启动类

public class NettyClient {
    public static void main(String[] args) throws Exception{
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
            // 服务器启动类
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(worker);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new ClientHandler());
               
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值