详解什么是 TCP 粘包和拆包现象并演示 Netty 是如何解决的

概述本文介绍什么是 TCP 粘包和拆包现象 , 并通过 Netty 编写详细的案例来重现 TCP 粘包问题 , 最后再通过一个 Netty 的 demo 来解决这个问题 。具体内容如下

  1. 什么是 TCP 粘包和拆包现象
  2. 重现 TCP 粘包和拆包现象
  3. Netty 解决 TCP 粘包和拆包现象带来的问题
什么是 TCP 粘包和拆包现象TCP 编程底层都有粘包和拆包机制 , 因为我们在C/S这种传输模型下 , 以TCP协议传输的时候 , 在网络中的byte其实就像是河水 , TCP就像一个搬运工,将这流水从一端转送到另一端 , 这时又分两种情况:
  1. 如果客户端的每次制造的水比较多 , 也就是我们常说的客户端给的包比较大 , TCP这个搬运工就会分多次去搬运
  2. 如果客户端每次制造的水比较少的话 , TCP可能会等客户端多次生产之后 , 把所有的水一起再运输到另一端
  • 对于第一种情况 , TCP 会再客户端先进行拆包 , 在另一端接收的时候 , 需要把多次获取的结果组合在一起 , 变成我们可以理解的信息
  • 对于第二种情况 , TCP 会在客户端先进行粘包 , 在另一端接收的时候 , 就必须进行拆包处理 , 因为每次接收的信息 , 可能是另一个远程端多次发送的包 , 被TCP粘在一起的
重现 TCP 粘包和拆包现象
  1. 通过在客户端 1 次发送超大数据包给服务器端来重现 TCP 拆包现象
  2. 通过在客户端分 10 次发送较小的数据包给服务器端来重现 TCP 粘包现象
下面通过 Netty 重现 TCP 粘包和拆包现象 。
Netty maven 依赖<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.76.Final</version></dependency>通过 Netty 重现 TCP 拆包现象
  1. Netty 客户端启动类:NettyClient
package com.ckJAVA.test.client;import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.ChannelPipeline;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NIOSocketChannel;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;@Slf4j@Componentpublic class NettyClient {static final String HOST = System.getProperty("host", "127.0.0.1");static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));public static void main(String[] args) throws Exception {// 初始化客户端事件组EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group)// 初始化通道.channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {// 初始化通道处理器@Overridepublic void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();p.addLast(new NettyClientHandler());}});b.connect(HOST, PORT).addListener(future -> {log.info(String.format("连接服务器端:%s:%s 成功!", HOST, PORT));}).await();} catch (Exception e) {log.error("启动客户端出现异常", e);}}}
  1. Netty 客户端通道处理类:NettyClientHandler
package com.ckjava.test.client;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import lombok.extern.slf4j.Slf4j;import java.nio.charset.StandardCharsets;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;@Slf4jpublic class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {private final AtomicInteger countRef = new AtomicInteger(0);//客户端读取服务器发送的信息@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {byte[] buffer = new byte[msg.readableBytes()];msg.readBytes(buffer);String message = new String(buffer, StandardCharsets.UTF_8);log.info(String.format("客户端接收到消息:[%s]", message));log.info(String.format("客户端接收到消息的次数:%s", countRef.accumulateAndGet(1, Integer::sum)));log.info("---------------------------------------------------");}// 重写 channelActive, 当客户端启动的时候 自动发送数据给服务端@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 客户端只发送一次 , 但是本次数据量很大// tcp 会将数据拆分成多份后依次进行发送String data = https://www.isolves.com/it/wl/zs/2022-04-24/"ckjava";StringBuilder stringBuilder = new StringBuilder(data);for (int i = 0; i


推荐阅读