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打印,收到多个包
![](https://images2015.cnblogs.com/blog/775480/201706/775480-20170618211956040-60675926.png)
Client打印,所有的回复都在一个包里面
![](https://images2015.cnblogs.com/blog/775480/201706/775480-20170618211956978-1860526592.png)
这难道就是传说中的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);; }
![](https://images2015.cnblogs.com/blog/775480/201706/775480-20170618211958681-1348650796.png)
瞄到在配置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
![](https://images2015.cnblogs.com/blog/775480/201706/775480-20170618212000212-1464637474.png)
怎么可能是null呢,明明 in有数据啊
![](https://images2015.cnblogs.com/blog/775480/201706/775480-20170618212001087-1305558503.png)
看来还得更深入,进入到 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端
![](https://images2015.cnblogs.com/blog/775480/201706/775480-20170618212002150-4073889.png)
时间精度为纳秒,两次打印存在时间间隔,证明不是同一条数据,也就证明 LineBasedFrameDecoder 解决了tcp粘包问题。
netty中解决tcp粘包问题的方案不止这一种,而且LineBasedFrameDecoder据说还有一种不要求携带结束符的解码方式,这些都留待下次再探讨。