2.1、netty 粘包、拆包(一)

Client 建立连接,发送一百条消息
    //在到服务器的连接已经建立之后将被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        for (int i = 0; i < 100; i++) {
        	byte[] req = "Query time ?".getBytes();
            ByteBuf message = Unpooled.buffer(req.length);
            message.writeBytes(req);
            ctx.writeAndFlush(message);
		}
    }

  

Server打印,收到多个包
 Client打印,所有的回复都在一个包里面
 这难道就是传说中的TCP粘包问题。
关于TCP粘包可以参考这篇文章:tcp粘包问题(经典分析)
延长两个包的间隔时间能不能处理呢?
在Client端加一个一秒的休眠
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        for (int i = 0; i < 100; i++) {
        	byte[] req = "Query time ?".getBytes();
            ByteBuf message = Unpooled.buffer(req.length);
            message.writeBytes(req);
            ctx.writeAndFlush(message);
            try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
    }

  

Server端打印:
服务器收到的请求1:Query time ?
服务器收到的请求2:Query time ?
服务器收到的请求3:Query time ?
服务器收到的请求4:Query time ?
省略92条记录。。。
服务器收到的请求97:Query time ?
服务器收到的请求98:Query time ?
服务器收到的请求99:Query time ?
服务器收到的请求100:Query time ?
Client打印
服务器时间 :Fri May 26 15:27:06 CST 2017Fri May 26 15:27:07 CST 2017Fri May 26 15:27:08 CST 2017Fri May 26 15:27:09 CST 2017Fri May 26 15:27:10 CST 2017Fri May 26 15:27:11 CST 2017Fri May 26 15:27:12 CST 2017Fri May 26 15:27:13 CST 2017Fri May 26 15:27:14 CST 2017Fri May 26 15:27:15 CST 2017Fri May 26 15:27:16 CST 2017Fri May 26 15:27:17 CST 2017Fri May 26 15:27:18 CST 2017Fri May 26 15:27:19 CST 2017Fri May 26 15:27:20 CST 2017Fri May 26 15:27:21 CST 2017Fri May 26 15:27:22 CST 2017Fri May 26 15:27:23 CST 2017Fri May 26 15:27:24 CST 2017Fri May 26 15:27:25 CST 2017Fri May 26 15:27:26 CST 2017Fri May 26 15:27:27 CST 2017Fri May 26 15:27:28 CST 2017Fri May 26 15:27:29 CST 2017Fri May 26 15:27:30 CST 2017Fri May 26 15:27:31 CST 2017Fri May 26 15:27:32 CST 2017Fri May 26 15:27:33 CST 2017Fri May 26 15:27:34 CST 2017Fri May 26 15:27:35 CST 2017Fri May 26 15:27:36 CST 2017Fri May 26 15:27:37 CST 2017Fri May 26 15:27:38 CST 2017Fri May 26 15:27:39 CST 2017Fri May 26 15:27:40 CST 2017Fri May 26 15:27:41 CST 2017Fri May 26 15:27
服务器时间 ::42 CST 2017Fri May 26 15:27:43 CST 2017Fri May 26 15:27:44 CST 2017Fri May 26 15:27:45 CST 2017Fri May 26 15:27:46 CST 2017Fri May 26 15:27:47 CST 2017Fri May 26 15:27:48 CST 2017Fri May 26 15:27:49 CST 2017Fri May 26 15:27:50 CST 2017Fri May 26 15:27:51 CST 2017Fri May 26 15:27:52 CST 2017Fri May 26 15:27:53 CST 2017Fri May 26 15:27:54 CST 2017Fri May 26 15:27:55 CST 2017Fri May 26 15:27:56 CST 2017Fri May 26 15:27:57 CST 2017Fri May 26 15:27:58 CST 2017Fri May 26 15:27:59 CST 2017Fri May 26 15:28:00 CST 2017Fri May 26 15:28:01 CST 2017Fri May 26 15:28:02 CST 2017Fri May 26 15:28:03 CST 2017Fri May 26 15:28:04 CST 2017Fri May 26 15:28:05 CST 2017Fri May 26 15:28:06 CST 2017Fri May 26 15:28:07 CST 2017Fri May 26 15:28:08 CST 2017Fri May 26 15:28:09 CST 2017Fri May 26 15:28:10 CST 2017Fri May 26 15:28:11 CST 2017Fri May 26 15:28:12 CST 2017Fri May 26 15:28:13 CST 2017Fri May 26 15:28:14 CST 2017Fri May 26 15:28:15 CST 2017Fri May 26 15:28:16 CST 2017Fri May 26 15:28:17 CST 2017Fri May 26 15:28:18 CST 2017Fri 
服务器时间 :May 26 15:28:19 CST 2017Fri May 26 15:28:20 CST 2017Fri May 26 15:28:21 CST 2017Fri May 26 15:28:22 CST 2017Fri May 26 15:28:23 CST 2017Fri May 26 15:28:24 CST 2017Fri May 26 15:28:25 CST 2017Fri May 26 15:28:26 CST 2017Fri May 26 15:28:27 CST 2017Fri May 26 15:28:28 CST 2017Fri May 26 15:28:29 CST 2017Fri May 26 15:28:30 CST 2017Fri May 26 15:28:31 CST 2017Fri May 26 15:28:32 CST 2017Fri May 26 15:28:33 CST 2017Fri May 26 15:28:34 CST 2017Fri May 26 15:28:35 CST 2017Fri May 26 15:28:36 CST 2017Fri May 26 15:28:37 CST 2017Fri May 26 15:28:38 CST 2017Fri May 26 15:28:39 CST 2017Fri May 26 15:28:40 CST 2017Fri May 26 15:28:41 CST 2017Fri May 26 15:28:42 CST 2017Fri May 26 15:28:43 CST 2017Fri May 26 15:28:44 CST 2017Fri May 26 15:28:45 CST 2017  
发送一百条请求,结果只收到三条回应,很多回应都放在了一个包里面,没有解决。
 
检查代码发现:
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
        ByteBuf buf = (ByteBuf)msg;
        try{
        	count++;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "UTF-8");
            System.out.println("服务器收到的请求" + count + ":" + body);
            String time = "Query time ?".equals(body) ? new Date().toString() : "fail";
            ByteBuf resp = Unpooled.copiedBuffer(time.getBytes());
            ctx.write(resp);
        }finally{
            buf.release();
        }
    }

  

代码里面用的是ctx.write(resp),是先写入缓存然后才写入channel的,将其改为 
ctx.writeAndFlush(resp);

  

再试一遍,
Client端打印
服务器时间 :Fri May 26 15:42:42 CST 2017Fri May 26 15:42:43 CST 2017Fri May 26 15:42:44 CST 2017Fri May 26 15:42:45 CST 2017Fri May 26 15:42:46 CST 2017Fri May 26 15:42:47 CST 2017Fri May 26 15:42:48 CST 2017Fri May 26 15:42:49 CST 2017Fri May 26 15:42:50 CST 2017Fri May 26 15:42:51 CST 2017Fri May 26 15:42:52 CST 2017Fri May 26 15:42:53 CST 2017Fri May 26 15:42:54 CST 2017Fri May 26 15:42:55 CST 2017Fri May 26 15:42:56 CST 2017Fri May 26 15:42:57 CST 2017Fri May 26 15:42:58 CST 2017Fri May 26 15:42:59 CST 2017Fri May 26 15:43:00 CST 2017Fri May 26 15:43:01 CST 2017Fri May 26 15:43:02 CST 2017Fri May 26 15:43:03 CST 2017Fri May 26 15:43:04 CST 2017Fri May 26 15:43:05 CST 2017Fri May 26 15:43:06 CST 2017Fri May 26 15:43:07 CST 2017Fri May 26 15:43:08 CST 2017Fri May 26 15:43:09 CST 2017Fri May 26 15:43:10 CST 2017Fri May 26 15:43:11 CST 2017Fri May 26 15:43:12 CST 2017Fri May 26 15:43:13 CST 2017Fri May 26 15:43:14 CST 2017Fri May 26 15:43:15 CST 2017Fri May 26 15:43:16 CST 2017Fri May 26 15:43:17 CST 2017Fri May 26 15:43
服务器时间 ::18 CST 2017Fri May 26 15:43:19 CST 2017Fri May 26 15:43:20 CST 2017Fri May 26 15:43:21 CST 2017Fri May 26 15:43:22 CST 2017Fri May 26 15:43:23 CST 2017Fri May 26 15:43:24 CST 2017Fri May 26 15:43:25 CST 2017Fri May 26 15:43:26 CST 2017Fri May 26 15:43:27 CST 2017Fri May 26 15:43:28 CST 2017Fri May 26 15:43:29 CST 2017Fri May 26 15:43:30 CST 2017Fri May 26 15:43:31 CST 2017Fri May 26 15:43:32 CST 2017Fri May 26 15:43:33 CST 2017Fri May 26 15:43:34 CST 2017Fri May 26 15:43:35 CST 2017Fri May 26 15:43:36 CST 2017Fri May 26 15:43:37 CST 2017Fri May 26 15:43:38 CST 2017Fri May 26 15:43:39 CST 2017Fri May 26 15:43:40 CST 2017Fri May 26 15:43:41 CST 2017Fri May 26 15:43:42 CST 2017Fri May 26 15:43:43 CST 2017Fri May 26 15:43:44 CST 2017Fri May 26 15:43:45 CST 2017Fri May 26 15:43:46 CST 2017Fri May 26 15:43:47 CST 2017Fri May 26 15:43:48 CST 2017Fri May 26 15:43:49 CST 2017Fri May 26 15:43:50 CST 2017Fri May 26 15:43:51 CST 2017Fri May 26 15:43:52 CST 2017Fri May 26 15:43:53 CST 2017Fri May 26 15:43:54 CST 2017Fri 
服务器时间 :May 26 15:43:55 CST 2017Fri May 26 15:43:56 CST 2017Fri May 26 15:43:57 CST 2017Fri May 26 15:43:58 CST 2017Fri May 26 15:43:59 CST 2017Fri May 26 15:44:00 CST 2017Fri May 26 15:44:01 CST 2017Fri May 26 15:44:02 CST 2017Fri May 26 15:44:03 CST 2017Fri May 26 15:44:04 CST 2017Fri May 26 15:44:05 CST 2017Fri May 26 15:44:06 CST 2017Fri May 26 15:44:07 CST 2017Fri May 26 15:44:08 CST 2017Fri May 26 15:44:09 CST 2017Fri May 26 15:44:10 CST 2017Fri May 26 15:44:11 CST 2017Fri May 26 15:44:12 CST 2017Fri May 26 15:44:13 CST 2017Fri May 26 15:44:14 CST 2017Fri May 26 15:44:15 CST 2017Fri May 26 15:44:16 CST 2017Fri May 26 15:44:17 CST 2017Fri May 26 15:44:18 CST 2017Fri May 26 15:44:19 CST 2017Fri May 26 15:44:20 CST 2017Fri May 26 15:44:21 CST 2017
 
看来问题不是出在这里。
两次都是三个回应,那么这个包的大小是固定的咯,那就测一下包的大小
	public static void main(String[] args) {
		String str = ":42 CST 2017Fri May 26 15:27:43 CST 2017Fri May 26 15:27:44 CST 2017Fri May 26 15:27:45 CST 2017Fri May 26 15:27:46 CST 2017Fri May 26 15:27:47 CST 2017Fri May 26 15:27:48 CST 2017Fri May 26 15:27:49 CST 2017Fri May 26 15:27:50 CST 2017Fri May 26 15:27:51 CST 2017Fri May 26 15:27:52 CST 2017Fri May 26 15:27:53 CST 2017Fri May 26 15:27:54 CST 2017Fri May 26 15:27:55 CST 2017Fri May 26 15:27:56 CST 2017Fri May 26 15:27:57 CST 2017Fri May 26 15:27:58 CST 2017Fri May 26 15:27:59 CST 2017Fri May 26 15:28:00 CST 2017Fri May 26 15:28:01 CST 2017Fri May 26 15:28:02 CST 2017Fri May 26 15:28:03 CST 2017Fri May 26 15:28:04 CST 2017Fri May 26 15:28:05 CST 2017Fri May 26 15:28:06 CST 2017Fri May 26 15:28:07 CST 2017Fri May 26 15:28:08 CST 2017Fri May 26 15:28:09 CST 2017Fri May 26 15:28:10 CST 2017Fri May 26 15:28:11 CST 2017Fri May 26 15:28:12 CST 2017Fri May 26 15:28:13 CST 2017Fri May 26 15:28:14 CST 2017Fri May 26 15:28:15 CST 2017Fri May 26 15:28:16 CST 2017Fri May 26 15:28:17 CST 2017Fri May 26 15:28:18 CST 2017Fri ";
		byte[] b = str.getBytes();
		System.out.println(b.length);;
	} 
 
瞄到在配置ServerBootstrap的参数时有个这样的配置
boot.group(bosser, worker)
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 1024)//配置TCP参数
            .childHandler(new ChannelInitializer<SocketChannel>(){//用于处理网络IO事件(记录日志,对消息进行编解码)
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new TimeServerHandler());
                }
            });

  

修改它为 128 试一下
.option(ChannelOption.SO_BACKLOG, 128)//配置TCP参数

  

结果现实是残酷的
服务器时间 :Fri May 26 15:56:44 CST 2017Fri May 26 15:56:45 CST 2017Fri May 26 15:56:46 CST 2017Fri May 26 15:56:47 CST 2017Fri May 26 15:56:48 CST 2017Fri May 26 15:56:49 CST 2017Fri May 26 15:56:50 CST 2017Fri May 26 15:56:51 CST 2017Fri May 26 15:56:52 CST 2017Fri May 26 15:56:53 CST 2017Fri May 26 15:56:54 CST 2017Fri May 26 15:56:55 CST 2017Fri May 26 15:56:56 CST 2017Fri May 26 15:56:57 CST 2017Fri May 26 15:56:58 CST 2017Fri May 26 15:56:59 CST 2017Fri May 26 15:57:00 CST 2017Fri May 26 15:57:01 CST 2017Fri May 26 15:57:02 CST 2017Fri May 26 15:57:03 CST 2017Fri May 26 15:57:04 CST 2017Fri May 26 15:57:05 CST 2017Fri May 26 15:57:06 CST 2017Fri May 26 15:57:07 CST 2017Fri May 26 15:57:08 CST 2017Fri May 26 15:57:09 CST 2017Fri May 26 15:57:10 CST 2017Fri May 26 15:57:11 CST 2017Fri May 26 15:57:12 CST 2017Fri May 26 15:57:13 CST 2017Fri May 26 15:57:14 CST 2017Fri May 26 15:57:15 CST 2017Fri May 26 15:57:16 CST 2017Fri May 26 15:57:17 CST 2017Fri May 26 15:57:18 CST 2017Fri May 26 15:57:19 CST 2017Fri May 26 15:57
服务器时间 ::20 CST 2017Fri May 26 15:57:21 CST 2017Fri May 26 15:57:22 CST 2017Fri May 26 15:57:23 CST 2017Fri May 26 15:57:24 CST 2017Fri May 26 15:57:25 CST 2017Fri May 26 15:57:26 CST 2017Fri May 26 15:57:27 CST 2017Fri May 26 15:57:28 CST 2017Fri May 26 15:57:29 CST 2017Fri May 26 15:57:30 CST 2017Fri May 26 15:57:31 CST 2017Fri May 26 15:57:32 CST 2017Fri May 26 15:57:33 CST 2017Fri May 26 15:57:34 CST 2017Fri May 26 15:57:35 CST 2017Fri May 26 15:57:36 CST 2017Fri May 26 15:57:37 CST 2017Fri May 26 15:57:38 CST 2017Fri May 26 15:57:39 CST 2017Fri May 26 15:57:40 CST 2017Fri May 26 15:57:41 CST 2017Fri May 26 15:57:42 CST 2017Fri May 26 15:57:43 CST 2017Fri May 26 15:57:44 CST 2017Fri May 26 15:57:45 CST 2017Fri May 26 15:57:46 CST 2017Fri May 26 15:57:47 CST 2017Fri May 26 15:57:48 CST 2017Fri May 26 15:57:49 CST 2017Fri May 26 15:57:50 CST 2017Fri May 26 15:57:51 CST 2017Fri May 26 15:57:52 CST 2017Fri May 26 15:57:53 CST 2017Fri May 26 15:57:54 CST 2017Fri May 26 15:57:55 CST 2017Fri May 26 15:57:56 CST 2017Fri 
服务器时间 :May 26 15:57:57 CST 2017Fri May 26 15:57:58 CST 2017Fri May 26 15:57:59 CST 2017Fri May 26 15:58:00 CST 2017Fri May 26 15:58:01 CST 2017Fri May 26 15:58:02 CST 2017Fri May 26 15:58:03 CST 2017Fri May 26 15:58:04 CST 2017Fri May 26 15:58:05 CST 2017Fri May 26 15:58:06 CST 2017Fri May 26 15:58:07 CST 2017Fri May 26 15:58:08 CST 2017Fri May 26 15:58:09 CST 2017Fri May 26 15:58:10 CST 2017Fri May 26 15:58:11 CST 2017Fri May 26 15:58:12 CST 2017Fri May 26 15:58:13 CST 2017Fri May 26 15:58:14 CST 2017Fri May 26 15:58:15 CST 2017Fri May 26 15:58:16 CST 2017Fri May 26 15:58:17 CST 2017Fri May 26 15:58:18 CST 2017Fri May 26 15:58:19 CST 2017Fri May 26 15:58:20 CST 2017Fri May 26 15:58:21 CST 2017Fri May 26 15:58:22 CST 2017Fri May 26 15:58:23 CST 2017

  

还是这样。
一查才才知道是socket的参数,ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
能不能禁用一下Nagle算法呢?(Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时
.childOption(ChannelOption.TCP_NODELAY, true)//禁用Nagle算法

  

结果还是一样。
 
看来解决问题的方向错了,TCP粘包不应该是某个参数决定的,而应该是一套解决方案,netty中定制解决方案一般是往ChannelPipeline上添加ChannelHandler
一搜就找到了LineBasedFrameDecoder 修改代码再试
			@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
					ch.pipeline().addLast(new TimeClientHandler());
				}

  

然后就没有然后了,两个服务器启动在那里,好像什么都没有发生。断点调试进入到LineBasedFrameDecoder
 怎么可能是null呢,明明 in有数据啊
看来还得更深入,进入到 LineBasedFrameDecoder 类的 findEndOfLine()方法
    /**
     * Returns the index in the buffer of the end of line found.
     * Returns -1 if no end of line was found in the buffer.
     */
    private static int findEndOfLine(final ByteBuf buffer) {
        int i = buffer.forEachByte(ByteProcessor.FIND_LF);
        if (i > 0 && buffer.getByte(i - 1) == '\r') {
            i--;
        }
        return i;
    }

  

得到 LineBasedFrameDecoder 是遍历ByteBuf中的可读字节,判断是否有 "\n" 或者 "\r\n" ,如果有,就以此位置为结束位置。
修改代码,加入换行
Server端
String time = "Query time ?".equals(body) ? new Date().toString() : "fail";
time = time + "\n";

  

Client端
byte[] req ="Query time ?\n".getBytes();

  

    并去掉线程休眠加入打印
System.out.println("接收结果 "+ count +", 时间"+System.nanoTime()+" 服务器时间 : "+ str);

  

控制台结果:
Server端
服务器收到的请求:1  Query time ?
服务器收到的请求:2  Query time ?
服务器收到的请求:3  Query time ?
服务器收到的请求:4  Query time ?
服务器收到的请求:5  Query time ?
省略90条。。。。
服务器收到的请求:96  Query time ?
服务器收到的请求:97  Query time ?
服务器收到的请求:98  Query time ?
服务器收到的请求:99  Query time ?
服务器收到的请求:100  Query time ?

  

Client端
 时间精度为纳秒,两次打印存在时间间隔,证明不是同一条数据,也就证明 LineBasedFrameDecoder 解决了tcp粘包问题。
netty中解决tcp粘包问题的方案不止这一种,而且LineBasedFrameDecoder据说还有一种不要求携带结束符的解码方式,这些都留待下次再探讨。
 

 

posted @ 2017-06-18 21:20  chenzl1024  阅读(297)  评论(0编辑  收藏  举报