Netty 学习笔记(七)、Netty 如何解决 TCP 粘包/拆包问题(二)

概述

上篇博客我简单介绍了粘包、拆包问题出现的原因以及 Netty 如何解决该问题,在博客最后我给出通过换行符分割数据包解决粘包问题的方案。然而在实际业务场景中,数据包可能不适用换行符分割这种方式,因为实际数据中可能包含换行符。本篇博客我就来简单补充两种更完整的解决方案:


DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder 类可以自定义分割符,一般在代码中这样使用:

ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
DelimiterBasedFrameDecoder decoder = new DelimiterBasedFrameDecoder(1024, buf);

上述代码就创建一种以 “$_” 为分隔符的缓冲池处理器,其中它的作用机制和 LineBasedFrameDecoder 类似,都是遍历 ByteBuf 中可读字节,根据关键字分割 TCP 包。其中1024表示最大长度,如果超过最大长度还没有出现分隔符就报错。

下面我们看一组具体示例,这里我们使用 Echo 服务器,其中我只列出核心代码:

Echo 服务端

bootstrap.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
                socketChannel.pipeline().addLast(
                        new DelimiterBasedFrameDecoder(1024, buf)
                );
                socketChannel.pipeline().addLast(new StringDecoder());
                socketChannel.pipeline().addLast(new EchoServerHandler());
            }
        });

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    String body = (String) msg;
    System.out.println("Time Server Receive message:[" + body + "] , and counter =  " + ++counter);
    String result = body + "$_";
    ByteBuf buf = Unpooled.copiedBuffer(result.getBytes());
    ctx.writeAndFlush(buf);
}

Echo 客户端

bootstrap.group(group)
        .channel(NioSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
                socketChannel.pipeline().addLast(
                        new DelimiterBasedFrameDecoder(1024, buf)
                );
                socketChannel.pipeline().addLast(new StringDecoder());
                socketChannel.pipeline().addLast(new EchoClientHandler());
            }
        });

private byte[] bytes = "Hi Netty!$_".getBytes();
 
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ByteBuf buf = null;
    for (int i = 0; i < 10; i++) {
        buf = Unpooled.buffer(bytes.length);
        buf.writeBytes(bytes);
        ctx.writeAndFlush(buf);
    }
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    String message = (String) msg;
    System.out.println("Time Client Receive message:[" + message + "] , and counter =  " + ++counter);
}

执行结果

服务端:
Time Server Receive message:[Hi Netty!] , and counter =  1
Time Server Receive message:[Hi Netty!] , and counter =  2
...
Time Server Receive message:[Hi Netty!] , and counter =  10

客户端:
Time Client Receive message:[Hi Netty!] , and counter =  1
Time Client Receive message:[Hi Netty!] , and counter =  2
...
Time Client Receive message:[Hi Netty!] , and counter =  10

从执行结果就可以看出,TCP 消息包在缓冲区被 DelimiterBasedFrameDecoder 按我们预设的关键词分割为多个数据包。


FixedLengthFrameDecoder

FixedLengthFrameDecoder 类一般用作定长数据包,其中它在代码中一般这样使用:

FixedLengthFrameDecoder decoder = new FixedLengthFrameDecoder(20);

上述代码就创建了一个按20字节长度为一数据包的处理器,其中它的机制和 DelimiterBasedFrameDecoder 类似。

下面我们具体看示例,同样采用 Echo 服务器,只列出核心代码:

Echo 服务端

bootstrap.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
                socketChannel.pipeline().addLast(
                        new FixedLengthFrameDecoder(20)
                );
                socketChannel.pipeline().addLast(new StringDecoder());
                socketChannel.pipeline().addLast(new EchoServerHandler());
            }
        });
        
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    String body = (String) msg;
    System.out.println("Time Server Receive message:[" + body + "] , and counter =  " + ++counter);
    ByteBuf buf = Unpooled.copiedBuffer(body.getBytes());
    ctx.writeAndFlush(buf);
}

Echo 客户端

 bootstrap.group(group)
         .channel(NioSocketChannel.class)
         .option(ChannelOption.TCP_NODELAY, true)
         .handler(new ChannelInitializer<SocketChannel>() {
             @Override
             protected void initChannel(SocketChannel socketChannel) throws Exception {
                 ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
                 socketChannel.pipeline().addLast(
                         new FixedLengthFrameDecoder(20)
                 );
                 socketChannel.pipeline().addLast(new StringDecoder());
                 socketChannel.pipeline().addLast(new EchoClientHandler());
             }
         });

private byte[] bytes = "Hello Netty !!!!!!!!".getBytes();         

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ByteBuf buf = null;
    for (int i = 0; i < 10; i++) {
        buf = Unpooled.buffer(bytes.length);
        buf.writeBytes(bytes);
        ctx.writeAndFlush(buf);
    }
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    String message = (String) msg;
    System.out.println("Time Client Receive message:[" + message + "] , and counter =  " + ++counter);
}

** 执行结果**:

服务端:
Time Server Receive message:[Hello Netty !!!!!!!!] , and counter =  1
Time Server Receive message:[Hello Netty !!!!!!!!] , and counter =  2
...
Time Server Receive message:[Hello Netty !!!!!!!!] , and counter =  10

客户端:
Time Client Receive message:[Hello Netty !!!!!!!!] , and counter =  1
Time Client Receive message:[Hello Netty !!!!!!!!] , and counter =  2
...
Time Client Receive message:[Hello Netty !!!!!!!!] , and counter =  10

上述示例中,我们每个数据包长度固定20字节,和 FixedLengthFrameDecoder 处理器配置长度相同,这样所有的数据包都可以完整分割,从执行结果来看也没有出现任何问题。

至此,Netty 常用的解决 TCP 粘包、拆包问题的 API 介绍完毕。


参考
《Netty 权威指南》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值