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据说还有一种不要求携带结束符的解码方式,这些都留待下次再探讨。

浙公网安备 33010602011771号