TCP 粘包/拆包说明 及 异常案例

TCP 粘包/ 拆包

TCP 是个“流”协议,所谓流,就是没有界限的一串数据,可以想象河里的流水连成一片,其间并没有分界线

TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分,业务上认为,一个完整的包可能会被 TCP 拆分为多个包,也可能把多个小的包封装成一个大的数据包,这就是所谓的拆包与粘包。

问题说明:


假设客户端先后分别给服务端发送了两个数据包 D1 与 D2,由于服务端一次性读取的字节数是不确定的,故可能存在以下5种情况:

1)服务端分两次读取到了两个独立的数据包,分别是 D1与 D2,没有粘包与拆包

2)服务端一次接收到了两个数据包,D1 与 D2 粘合在一起,被称为 TCP 粘包

3)服务端分两次读取到了两个数据包,第一次读取到了完整的 D1 包和 D2 包的部分内容,第二次读取到了 D2 包的剩余内容,这被称为 TCP 拆包

4)服务端分两次读取到了两个数据包,第一次读取到了 D1 包部分内容,第二次读取到了 D1 包的剩余内容和 D2 包的整包,这同样是TCP拆包

5)如果此时服务端 TCP 接收滑窗非常小,而数据包 D1 与 D2 又比较大,故服务端很可能分多次才能将 D1 和 D2 包接收完全,其间发生多次拆包

问题产生的原因:


问题产生的原因有分别有如下三个:

1)应用程序 write 写入的字节大小大于套接口发送缓冲区大小。

2)进行 MSS 大小的 TCP 分段。MSS(Maximum Segment Size,最大报文长度)是 TCP 协议定义的一个选项,MSS 选项用于在 TCP 连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。

3)以太网帧的 payload 大于 MTU 进行 IP 分片

解决策略:


由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,所以上层应用协议为了对消息进行区分,往往采用如下 4 种方式:

1)消息长度固定,累积读取到长度总和为定长 LEN 的报文后,就认为读取到了一个完整的消息,再将计数器置位,重新读取下一个数据报。例如可以让每个报文的大小为固定长度 1024 字节,如果不够,空位补空格;

2)将回车换行符作为消息结束符,例如 FTP 协议,这种方式在文本协议中应用比较广泛。

3)自定义分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符;

4)将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息长度)的字段,通常设计思路为消息头的第一个字段使用 int32 来表示消息的总长度。

Netty 对上面 4 种应用做了统一的抽象,提供了 4 种解码器来解决对应的问题,使用起来非常方便,有了这些解码器,程序员不需要自己对读取的报文进行人工解码,也不需要考虑 TCP 粘包与拆包。这些加码器后续文章会进行讲解,本文先演示一个未进行 TCP 粘包与拆包处理的异常案例。

异常案例

在《Netty 入门示例详解》中客户端给服务器发送一条消息后,服务器回复,然后客户端接收后自己退出,所以没有问题。

而如果一个客户端连接连续不断的发送 100 条消息给服务器,百分之九十九以上会发生粘包与拆包,即功能异常;一百个客户端分别发送 1 条消息给服务器,亲测结果没有发生粘包与拆包。

即每个客户端发送消息时,如果连续不断的发送多条消息,就容易出事,如果在发送下一条消息前延时一定的时间,如 Thread.sleep(500),问题也可以得到缓解。

本文对《Netty 入门示例详解》中的代码稍微修改。

一百个客户端各发一条

这种情况亲测没有问题,即多个客户端同时发送消息,但是每个客户端每次只发一条时,不会有事。

TimeServer

package com.lct.netty.stickyBag;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Created by Administrator on 2017/5/16.
 */
public class TimeServer {
    public static void main(String[] args) {
        int port = 9898;
        new TimeServer().bind(port);
    }

    public void bind(int port) {
        /**
         * interface EventLoopGroup extends EventExecutorGroup extends ScheduledExecutorService extends ExecutorService
         * 配置服务端的 NIO 线程池,用于网络事件处理,实质上他们就是 Reactor 线程组
         * bossGroup 用于服务端接受客户端连接,workerGroup 用于进行 SocketChannel 网络读写*/
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /** ServerBootstrap 是 Netty 用于启动 NIO 服务端的辅助启动类,用于降低开发难度
             * */
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());

            /**服务器启动辅助类配置完成后,调用 bind 方法绑定监听端口,调用 sync 方法同步等待绑定操作完成*/
            ChannelFuture f = b.bind(port).sync();

            System.out.println(Thread.currentThread().getName() + ",服务器开始监听端口,等待客户端连接.........");
            /**下面会进行阻塞,等待服务器连接关闭之后 main 方法退出,程序结束
             *
             * */
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            /**优雅退出,释放线程池资源*/
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            arg0.pipeline().addLast(new TimeServerHandler());
        }
    }
}

TimeServerHandler

package com.lct.netty.stickyBag;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Administrator on 2017/5/16.
 * ChannelInboundHandlerAdapter extends ChannelHandlerAdapter 用于对网络事件进行读写操作
 */
public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 因为多线程,所以使用原子操作类来进行计数
     */
    private static AtomicInteger atomicInteger = new AtomicInteger();

    /**
     * 收到客户端消息,自动触发
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        /**
         * 将 msg 转为 Netty 的 ByteBuf 对象,类似 JDK 中的 java.nio.ByteBuffer,不过 ButeBuf 功能更强,更灵活
         */
        ByteBuf buf = (ByteBuf) msg;
        /**readableBytes:获取缓冲区可读字节数,然后创建字节数组
         * 从而避免了像 java.nio.ByteBuffer 时,只能盲目的创建特定大小的字节数组,比如 1024
         * */
        byte[] reg = new byte[buf.readableBytes()];
        /**readBytes:将缓冲区字节数组复制到新建的 byte 数组中
         * 然后将字节数组转为字符串
         * */
        buf.readBytes(reg);
        String body = new String(reg, "UTF-8");
        System.out.println((atomicInteger.addAndGet(1)) + "--->" + Thread.currentThread().getName() + ",The server receive  order : " + body);

        /**回复消息
         * copiedBuffer:创建一个新的缓冲区,内容为里面的参数
         * 通过 ChannelHandlerContext 的 write 方法将消息异步发送给客户端
         * */
        String respMsg = "I am Server,消息接收 success!" + Thread.currentThread().getName();
        ByteBuf respByteBuf = Unpooled.copiedBuffer(respMsg.getBytes());
        ctx.write(respByteBuf);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        /**flush:将消息发送队列中的消息写入到 SocketChannel 中发送给对方,为了频繁的唤醒 Selector 进行消息发送
         * Netty 的 write 方法并不直接将消息写如 SocketChannel 中,调用 write 只是把待发送的消息放到发送缓存数组中,再通过调用 flush
         * 方法,将发送缓冲区的消息全部写入到 SocketChannel 中
         * */
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**当发生异常时,关闭 ChannelHandlerContext,释放和它相关联的句柄等资源 */
        ctx.close();
    }
}

TimeClient

package com.lct.netty.stickyBag;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * Created by Administrator on 2017/5/16.
 */
public class TimeClient {

    /**
     * 使用 100 个线程模拟 100 个客户端
     *
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new MyThread()).start();
        }
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            connect("192.168.1.20", 9898);
        }

        public void connect(String host, int port) {
            /**配置客户端 NIO 线程组/池*/
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                /**Bootstrap 与 ServerBootstrap 都继承(extends)于 AbstractBootstrap
                 * 创建客户端辅助启动类,并对其配置,与服务器稍微不同,这里的 Channel 设置为 NioSocketChannel
                 * 然后为其添加 Handler,这里直接使用匿名内部类,实现 initChannel 方法
                 * 作用是当创建 NioSocketChannel 成功后,在进行初始化时,将它的ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件*/
                Bootstrap b = new Bootstrap();
                b.group(group).channel(NioSocketChannel.class)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new TimeClientHandler());
                            }
                        });

                /**connect:发起异步连接操作,调用同步方法 sync 等待连接成功*/
                ChannelFuture channelFuture = b.connect(host, port).sync();
//                System.out.println(Thread.currentThread().getName() + ",客户端发起异步连接..........");

                /**等待客户端链路关闭*/
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                /**优雅退出,释放NIO线程组*/
                group.shutdownGracefully();
            }
        }
    }
}

TimeClientHandler

package com.lct.netty.stickyBag;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * Created by Administrator on 2017/5/17.
 * 用于对网络事件进行读写操作
 */
public class TimeClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 因为 Netty 采用线程池,所以这里使用原子操作类来进行计数
     */
    private static AtomicInteger atomicInteger = new AtomicInteger();

    /**
     * 当客户端和服务端 TCP 链路建立成功之后,Netty 的 NIO 线程会调用 channelActive 方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String reqMsg = "我是客户端 " + Thread.currentThread().getName();
        byte[] reqMsgByte = reqMsg.getBytes("UTF-8");
        ByteBuf reqByteBuf = Unpooled.buffer(reqMsgByte.length);
        /**
         * writeBytes:将指定的源数组的数据传输到缓冲区
         * 调用 ChannelHandlerContext 的 writeAndFlush 方法将消息发送给服务器
         */
        reqByteBuf.writeBytes(reqMsgByte);
        ctx.writeAndFlush(reqByteBuf);
    }

    /**
     * 当服务端返回应答消息时,channelRead 方法被调用,从 Netty 的 ByteBuf 中读取并打印应答消息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println((atomicInteger.addAndGet(1)) + "---" + Thread.currentThread().getName() + ",Server return Message:" + body);
        ctx.close();
    }

    /**
     * 当发生异常时,打印异常 日志,释放客户端资源
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**释放资源*/
        ctx.close();
    }
}

运行测试

同样先运行服务器,再运行客户端。

服务器控制台输出如下:

main,服务器开始监听端口,等待客户端连接.........
1--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-93-1
5--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-84-1
7--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-85-1
3--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-19-1
8--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-88-1
4--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-51-1
2--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-39-1
6--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-97-1
9--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-21-1
10--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-49-1
11--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-30-1
12--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-44-1
14--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-68-1
15--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-54-1
16--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-90-1
17--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-98-1
18--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-75-1
19--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-100-1
20--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-35-1
21--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-13-1
22--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-40-1
23--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-55-1
25--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-4-1
26--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-56-1
27--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-73-1
28--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-67-1
29--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-76-1
30--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-18-1
31--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-11-1
32--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-52-1
33--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-28-1
34--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-10-1
35--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-70-1
36--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-69-1
37--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-22-1
38--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-5-1
39--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-33-1
40--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-8-1
41--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-87-1
42--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-63-1
43--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-14-1
47--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-29-1
13--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-78-1
49--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-38-1
48--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-36-1
53--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-58-1
54--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-81-1
55--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-66-1
56--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-20-1
46--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-79-1
57--->nioEventLoopGroup-3-3,The server receive  order : 我是客户端 nioEventLoopGroup-2-1
45--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-65-1
59--->nioEventLoopGroup-3-7,The server receive  order : 我是客户端 nioEventLoopGroup-3-1
44--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-96-1
60--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-74-1
61--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-42-1
62--->nioEventLoopGroup-3-4,The server receive  order : 我是客户端 nioEventLoopGroup-23-1
24--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-61-1
63--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-95-1
64--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-46-1
65--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-60-1
66--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-62-1
58--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-6-1
67--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-26-1
69--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-101-1
70--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-37-1
71--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-83-1
72--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-99-1
73--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-41-1
74--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-47-1
75--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-91-1
52--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-15-1
76--->nioEventLoopGroup-3-5,The server receive  order : 我是客户端 nioEventLoopGroup-12-1
51--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-25-1
78--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-72-1
79--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-17-1
80--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-45-1
81--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-71-1
50--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-24-1
82--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-50-1
83--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-43-1
84--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-86-1
77--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-82-1
85--->nioEventLoopGroup-3-6,The server receive  order : 我是客户端 nioEventLoopGroup-80-1
86--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-9-1
87--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-92-1
88--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-77-1
89--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-48-1
90--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-94-1
91--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-59-1
92--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-27-1
93--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-89-1
94--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-64-1
95--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-57-1
97--->nioEventLoopGroup-3-1,The server receive  order : 我是客户端 nioEventLoopGroup-7-1
68--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-32-1
96--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-34-1
98--->nioEventLoopGroup-3-8,The server receive  order : 我是客户端 nioEventLoopGroup-16-1
99--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-31-1
100--->nioEventLoopGroup-3-2,The server receive  order : 我是客户端 nioEventLoopGroup-53-1

客户端控制台输出如下:

2---nioEventLoopGroup-93-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
8---nioEventLoopGroup-97-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
9---nioEventLoopGroup-39-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
1---nioEventLoopGroup-19-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
3---nioEventLoopGroup-49-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
7---nioEventLoopGroup-21-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
6---nioEventLoopGroup-51-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
4---nioEventLoopGroup-88-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
5---nioEventLoopGroup-85-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
10---nioEventLoopGroup-30-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
11---nioEventLoopGroup-44-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
12---nioEventLoopGroup-68-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
13---nioEventLoopGroup-90-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
14---nioEventLoopGroup-54-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
15---nioEventLoopGroup-98-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
16---nioEventLoopGroup-75-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
17---nioEventLoopGroup-100-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
18---nioEventLoopGroup-35-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
19---nioEventLoopGroup-13-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
20---nioEventLoopGroup-40-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
21---nioEventLoopGroup-55-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
22---nioEventLoopGroup-4-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
23---nioEventLoopGroup-56-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
24---nioEventLoopGroup-84-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
25---nioEventLoopGroup-73-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
26---nioEventLoopGroup-67-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
27---nioEventLoopGroup-76-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
28---nioEventLoopGroup-18-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
29---nioEventLoopGroup-11-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
30---nioEventLoopGroup-52-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
31---nioEventLoopGroup-28-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
32---nioEventLoopGroup-10-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
33---nioEventLoopGroup-70-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
34---nioEventLoopGroup-69-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
35---nioEventLoopGroup-22-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
36---nioEventLoopGroup-5-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
37---nioEventLoopGroup-33-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
38---nioEventLoopGroup-8-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
100---nioEventLoopGroup-53-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
99---nioEventLoopGroup-31-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
98---nioEventLoopGroup-32-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
97---nioEventLoopGroup-16-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
96---nioEventLoopGroup-34-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
95---nioEventLoopGroup-7-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
94---nioEventLoopGroup-57-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
93---nioEventLoopGroup-64-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
92---nioEventLoopGroup-89-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
91---nioEventLoopGroup-27-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
90---nioEventLoopGroup-59-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
89---nioEventLoopGroup-94-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
88---nioEventLoopGroup-48-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
87---nioEventLoopGroup-77-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
86---nioEventLoopGroup-92-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
85---nioEventLoopGroup-9-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
84---nioEventLoopGroup-80-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
83---nioEventLoopGroup-82-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
82---nioEventLoopGroup-86-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
81---nioEventLoopGroup-43-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
80---nioEventLoopGroup-50-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
79---nioEventLoopGroup-24-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
78---nioEventLoopGroup-71-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
77---nioEventLoopGroup-45-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
76---nioEventLoopGroup-17-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
75---nioEventLoopGroup-72-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
74---nioEventLoopGroup-25-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
73---nioEventLoopGroup-12-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
71---nioEventLoopGroup-15-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
72---nioEventLoopGroup-91-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
70---nioEventLoopGroup-47-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
69---nioEventLoopGroup-41-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
68---nioEventLoopGroup-99-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
67---nioEventLoopGroup-83-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
66---nioEventLoopGroup-37-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
65---nioEventLoopGroup-101-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
64---nioEventLoopGroup-26-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
63---nioEventLoopGroup-6-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
62---nioEventLoopGroup-62-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
61---nioEventLoopGroup-60-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
60---nioEventLoopGroup-46-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
59---nioEventLoopGroup-95-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
58---nioEventLoopGroup-61-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-2
57---nioEventLoopGroup-23-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
56---nioEventLoopGroup-42-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
55---nioEventLoopGroup-74-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
54---nioEventLoopGroup-96-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-4
53---nioEventLoopGroup-3-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
52---nioEventLoopGroup-65-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-7
51---nioEventLoopGroup-2-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
50---nioEventLoopGroup-79-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-5
49---nioEventLoopGroup-20-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
48---nioEventLoopGroup-66-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
47---nioEventLoopGroup-81-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
46---nioEventLoopGroup-58-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
45---nioEventLoopGroup-36-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
44---nioEventLoopGroup-38-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-8
43---nioEventLoopGroup-78-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-6
42---nioEventLoopGroup-29-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
41---nioEventLoopGroup-14-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3
40---nioEventLoopGroup-63-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1
39---nioEventLoopGroup-87-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-1

结论:事实证明,多个客户端同时给服务器发送消息时,只要客户端不连续发送,服务器读取和回复是没有问题的。

一百个客户端发一百条

代码与上面一致的的部分,这里不再贴出,以下是修改的内容。

TimeClient 类修改如下:

public class TimeClient {

    /**
     * 使用 1 个线程模拟 1 个客户端
     *
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 1; i++) {
            new Thread(new MyThread()).start();
        }
    }
...............

TimeClientHandler 修改内容如下:

...........
    /**
     * 当客户端和服务端 TCP 链路建立成功之后,Netty 的 NIO 线程会调用 channelActive 方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        /**
         * 连续发送 5 条数据 
         */
        for (int i = 0; i < 5; i++) {
            String reqMsg = (i + 1) + ",我是客户端 " + Thread.currentThread().getName();
            byte[] reqMsgByte = reqMsg.getBytes("UTF-8");
            ByteBuf reqByteBuf = Unpooled.buffer(reqMsgByte.length);
            /**
             * writeBytes:将指定的源数组的数据传输到缓冲区
             * 调用 ChannelHandlerContext 的 writeAndFlush 方法将消息发送给服务器
             */
            reqByteBuf.writeBytes(reqMsgByte);
            ctx.writeAndFlush(reqByteBuf);
        }
    }
...........

测试运行

连续不断的发送 5 条消息给服务器,测试结果如下:

服务器输出如下:

5--->nioEventLoopGroup-3-3,The server receive  order : 1,我是客户端 nioEventLoopGroup-2-1
6--->nioEventLoopGroup-3-3,The server receive  order : 2,我是客户端 nioEventLoopGroup-2-1
7--->nioEventLoopGroup-3-3,The server receive  order : 3,我是客户端 nioEventLoopGroup-2-14,我是客户端 nioEventLoopGroup-2-15,我是客户端 nioEventLoopGroup-2-1

客户端输出如下:

1---nioEventLoopGroup-2-1,Server return Message:I am Server,消息接收 success!nioEventLoopGroup-3-3I am Server,消息接收 success!nioEventLoopGroup-3-3I am Server,消息接收 success!nioEventLoopGroup-3-3

服务器理论上应该收到 5 条消息,可实际上只收到 3 条,这说明发生了 TCP 粘包。

客户端理论上收到服务器的回复应该是 3 条,实际上只收到 1 条,也说明发生了 TCP 粘包。

延迟发送

TimeClientHandler 中发送消息的时候,可以让其间隔细微的时间,如下所示,延迟 0.5 秒。

......
/**
     * 当客户端和服务端 TCP 链路建立成功之后,Netty 的 NIO 线程会调用 channelActive 方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        /**
         * 连续发送 5 条数据
         */
        for (int i = 0; i < 5; i++) {
            String reqMsg = (i + 1) + ",我是客户端 " + Thread.currentThread().getName();
            byte[] reqMsgByte = reqMsg.getBytes("UTF-8");
            ByteBuf reqByteBuf = Unpooled.buffer(reqMsgByte.length);
            /**
             * writeBytes:将指定的源数组的数据传输到缓冲区
             * 调用 ChannelHandlerContext 的 writeAndFlush 方法将消息发送给服务器
             */
            reqByteBuf.writeBytes(reqMsgByte);
            ctx.writeAndFlush(reqByteBuf);

            /**每发送 1 条消息,延迟 0.5 秒*/
            Thread.sleep(500);
        }
    }
......

测试结果,这样服务器在接收消息时,就一定程度上可以避免发生 TCP 粘包,而客户端读取服务器的回复仍然无法避免。

服务器输出如下:

main,服务器开始监听端口,等待客户端连接.........
1--->nioEventLoopGroup-3-1,The server receive  order : 1,我是客户端 nioEventLoopGroup-2-1
2--->nioEventLoopGroup-3-1,The server receive  order : 2,我是客户端 nioEventLoopGroup-2-1
3--->nioEventLoopGroup-3-1,The server receive  order : 3,我是客户端 nioEventLoopGroup-2-1
4--->nioEventLoopGroup-3-1,The server receive  order : 4,我是客户端 nioEventLoopGroup-2-1
5--->nioEventLoopGroup-3-1,The server receive  order : 5,我是客户端 nioEventLoopGroup-2-1

下一篇《LineBasedFrameDecoder 行解码器,回车换行符解决 TCP 粘包

猜你喜欢

转载自blog.csdn.net/wangmx1993328/article/details/83827963