Netty编程(七)—— 粘包半包(一)

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

粘包现象

以下面这个例子来说一下什么是粘包,下面这个是一个客户端的代码,它的任务很简单,就是向服务端连发十次0到15(十进制的字节00到0f):

public class StudyClient {
    static final Logger log = LoggerFactory.getLogger(StudyClient.class);
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(worker);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.debug("connected...");
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("sending...");
                            // 每次发送16个字节的数据,共发送10次
                            for (int i = 0; i < 10; i++) {
                                ByteBuf buffer = ctx.alloc().buffer();
                                buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                ctx.writeAndFlush(buffer);
                            }
                        }
                    });
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            log.error("client error", e);
        } finally {
            worker.shutdownGracefully();
        }
    }
}
复制代码

按理来说服务端应该收到十条消息,每条消息都是0到15(00到0f),但实际上结果是下面的:

在这里插入图片描述 可见虽然客户端是分别以16字节为单位,通过channel向服务器发送了10次数据,可是服务器端却只接收了一次,接收数据的大小为160B,即客户端发送的数据总大小,这就是粘包现象

半包现象

将客户端-服务器之间的channel容量进行调整:

// 调整channel的容量
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
复制代码

注意

serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍

这个时候的结果是:

在这里插入图片描述 可见客户端每次发送的数据,因channel容量不足,无法将发送的数据一次性接收,便产生了半包现象

现象分析

粘包

  • 现象
    • 发送 abc、def,接收 abcdef
  • 原因
    • 应用层
      • 接收方 ByteBuf 设置太大(Netty 默认 1024)
    • 传输层-网络层
      • 滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大(大于256 bytes),这 256 bytes 字节就会缓冲在接收方的滑动窗口中, 当滑动窗口中缓冲了多个报文就会粘包
      • Nagle 算法(如果一次发送的数据比较少,就等待其他数据一起发):会造成粘包

半包

  • 现象
    • 发送 abcdef,接收 abc、def
  • 原因
    • 应用层
      • 接收方 ByteBuf 小于实际发送数据量
    • 传输层-网络层
      • 滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时接收方窗口中无法容纳发送方的全部报文,发送方只能先发送前 128 bytes,等待 ack 后才能发送剩余部分,这就造成了半包
    • 数据链路层
      • MSS 限制(一次能发送最大长度 ):当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包

本质

发生粘包与半包现象的本质是因为 TCP 是流式协议,消息无边界

Guess you like

Origin juejin.im/post/7034886166457352205