netty-netty粘包和拆包问题

netty-netty粘包和拆包

摘自<netty权威指南>

为什么会有粘包和拆包的问题

netty粘包和拆包的问题,本质上归结于TCP的粘包和拆包。

网络上发送一个完整的数据包时,可能会被TCP拆分成多个包进行发送,也可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP的粘包和拆包问题

TCP的粘包和拆包的情况

现有客户端发送两个数据包P1和P2给服务器端,由于服务端一次读取的字节数是不确定的

TCP的粘包和拆包的情况:

  • 服务器端两次读到两个独立的包,分别是P1和P2,没有粘包和拆包的情况
  • 服务器端一次接收两个数据包,P1和P2粘合在一起被称为TCP粘包
  • 服务器端分两次读取到两个数据包,第一次读取到了完整的P1和P2包的部分内容(P2_1),第二次读取到了P2包的剩余内容(P2_2),称为TCP的拆包
  • 服务器端分两次读取到两个数据包,第一次读取到了P1包的部分内容(P1_1),第二次读取到了P1包的剩余内容(P1_2)和完整的P2包,称为TCP的拆包
  • 如果P1和P2包比较大,服务器端需要分多次才能将D1和D2包接收完全,期间会发生多次拆包

粘包问题解决方案

  • 消息定长,如每个报文的大小为固定长度200字节,如果不够,空位补空格
  • 在包尾增加回车换行进行分隔,如FTP协议
  • 将消息分成消息头和消息体。消息头中包含消息总长度(或者消息长度)的字段。通常设计思路为消息头的第一个字段使用int32表示消息的长度

粘包例子

改造一下TimeServerHandler:

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

	private int counter;

	@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").substring(0, req.length-
						System.getProperty("line.separator").length());
		System.out.println("time server recv order [x]: " 
				+ body + ",the counter is : " + counter++ + ";\n");

		String currentTime = body.startsWith("time") ? 
		new Date(System.currentTimeMillis()).toString(): "bad time ";
		ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
		ctx.write(resp);
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) 
					throws Exception {
		ctx.flush();
//		ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
//				.addListener(ChannelFutureListener.CLOSE);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, 
							Throwable cause) throws Exception {
		ctx.close();
	}
}

改造一下TimeClientHandler:

public class TimeClientHandler extends ChannelInboundHandlerAdapter {

	private int counter;

	@Override
	public void channelActive(ChannelHandlerContext ctx) 
											throws Exception {
		ByteBuf message = null;
		message = Unpooled.buffer();
		message.writeBytes("time".getBytes());
		ctx.writeAndFlush(message);
		for (int i = 0; i < 100; i++) {
			String body = " request order ";
			body = body + i + System.getProperty("line.separator");
			message = Unpooled.buffer(body.getBytes().length);
			message.writeBytes(body.getBytes());
			ctx.writeAndFlush(message);
		}
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, 
								Object msg) throws Exception {
		ByteBuf buf = (ByteBuf) msg;
		byte[] bytes = new byte[buf.readableBytes()];
		buf.readBytes(bytes);
		String body = new String(bytes, "utf-8");
		System.out.println("client recv now is [x] :" + body 
							+ ",the counter is:  " + counter++ + "\n");

	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, 
							Throwable cause) throws Exception {
		ctx.close();
	}
}

正常情况下服务器端接收到最后一条数据应该是这样的,即order为99,而counter为99:

time server recv order [x]:  request order 99,the counter is : 99;

但是,实际可能接收到的是这样的结果:即order为99,而counter为43。客户端总共发送了100次请求,而服务器端只接收只用了43次

time server recv order [x]:  request order 99,the counter is : 43;
client established success...
client recv now is [x] :bad time ... time ,the counter is:  0

client recv now is [x] :bad time ... time ,the counter is:  1

说明发生TCP粘包的情况

LineBasedFrameDecoder+StringDecoder解决粘包问题

ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());

LineBasedFrameDecoder原理

依次遍历ByteBuf字节,判断是否是以\n或者是\r\n结尾,如果找到,则以此位置为结束位置。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略之前读到的异常码流

StringDecoder原理

将接收的对象直接转成string类型

LineBasedFrameDecoder+StringDecoder组合就是换行切换的文本解码器

发布了76 篇原创文章 · 获赞 66 · 访问量 51万+

猜你喜欢

转载自blog.csdn.net/u013887008/article/details/104170006