netty中的粘包和拆包问题处理

1、tcp粘包和拆包问题说明

    我们可以通过下图来说明tcp粘包和拆包问题的说明:
 

    假设客户端发送d1和d2两个数据包到服务端,此时服务端收到包有如下几种情况:
    1:服务端正常收到d1和d2两个数据包;
    2:服务端收到一个数据包即是D1和D2粘在一起,此时称为粘包;
    3:服务端分2次收到2个数据包,第一次是d1的完整包和d2的部分包,第二次是d2剩余的包,此时就是拆包;
    4:服务端分2次收到2个数据包,第一次是d1的部分包,第二次是d1的剩余包和d2的包。
    还有一种情况是数据包较大,但tcp的接收窗口非常小,此时服务端要分多次才能收完D1和D2的数据包。

2、tcp粘包和拆包发生的原因

      1、应用程序write写入的字节大小大于套接口发送缓冲区大小;
    2、进行Mss大小的tcp分段;
    3、以太网贞的payload大于MTU进行ip分片。


3、粘包问题的解决策略

     由于底层的tcp无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计解决。根据业界的主流协议的解决方案,可以归纳如下:
     1)消息定长,例如每个报文大小长度200字节,如果不够,空位补空格;
     2)在包尾增加回车换行进行分割,例如ftp协议;
     3)将消息分为消息头和消息体,消息头包含表示消息总长度的字段,通常设计思路为消息头的第一字段使用int32来           表示消息的总长度;
     4)更复杂的应用层协议;

4、tcp粘包异常案例

   这里改造下timeServer,在收到消息的时候去除回车换行符。
package zou;

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

public class TimeServerHandler extends ChannelHandlerAdapter {

	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());
		//String body = (String) msg;
		System.out.println("the time server receive order :" + body + "the counter:" + ++counter);
		String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
		currentTime += System.getProperty("line.separator");
		ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
		//进行发送消息到客户端
		ctx.writeAndFlush(resp);
	}

	//	@Override
	//	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
	//		//通过调用此方法,将发送的缓冲区的消息全部写到SocketChannel中
	//		ctx.flush();
	//	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
	}
}
  timeClientHandler如下:
public class TimeClientHandle extends ChannelHandlerAdapter {

	private static final Logger logger = Logger.getLogger(TimeClientHandle.class.getName());

	private int counter;

	private byte[] req;

	public TimeClientHandle() {
		req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();

	}

	//连接成功后发送指令
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ByteBuf message = null;
		for (int i = 0; i < 100; i++) {
			message = Unpooled.buffer(req.length);
			message.writeBytes(req);
			ctx.writeAndFlush(message);
		}

	}

	@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");
		//String body = (String) msg;
		System.out.println("Now is:" + body + "the counter is " + ++counter);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
	}
}
   这里会进行发送100次查询时间的消息,按道理服务端和客户端都应该有100次的显示,但执行后结果如下:
   the time server receive order :QUERY TIME ORDER
QUERY TIME ORDER
。。。。。
the time server receive order :Y TIME ORDER
 客户端执行如下:
Now is:BAD ORDER
BAD ORDER
the counter is 1
 此时说明已经产生了粘包。

5、利用LineBaseFrameDecoder解决tcp粘包问题

为了解决tcp的粘包/拆包问题,netty默认提供了多种编码解码器用于处理半包。只要熟练掌握这些类库的使用,tcp粘包问题从此就会变得非常容易。调整点如下,TimeServer进行handler的初始化如下:
	private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
		@Override
		protected void initChannel(SocketChannel arg0) throws Exception {
			arg0.pipeline().addLast(new LineBasedFrameDecoder(1024));
			arg0.pipeline().addLast(new StringDecoder());
			arg0.pipeline().addLast(new TimeServerHandler());
		}
	}
   这里添加了2个编码器,lineBaseFrameDecode和StringDecoder,后面的TimeServerHandler 读取消息调整如下:
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		//读取客户端发送的字节
		String body = (String) msg;
		System.out.println("the time server receive order :" + body + "the counter:" + ++counter);
		String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
		currentTime += System.getProperty("line.separator");
		ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
		//进行发送消息到客户端
		ctx.writeAndFlush(resp);
	}
  此时代码很简洁,直接将消息转换为字符串,且已经是去除回车换行的。TimeClient的调整如下,也是增加解码器:
protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
					ch.pipeline().addLast(new StringDecoder());
					ch.pipeline().addLast(new TimeClientHandle());
				}
其timeClientHandler进行解码如下:
@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		String body = (String) msg;
		System.out.println("Now is:" + body + "the counter is " + ++counter);
	}

   非常简洁。相对于之前的调整有增加了解码器,另外在进行读取消息时候代码也简洁了很多,不用编码解码。
  进行运行发现没有产生tcp粘包的问题。


6、LineBaseFrameDecoder和StringDecoder原理分析

    LineBaseFrameDecoder的原理就是依次遍历ByteBuf中的可读字节,判断是否有“\n”或者是“\r\n”,如果有就以此为换行。另外它也能指定长度,如果在指定的长度内没有换行符那么就会抛出异常。
    StringDecoder非常简单,就是将接收到对象转换为字符串,然后继续后面的Handler调用。LineBaseFrameDecoder和StringDecoder结合起来使用就是按行切换的文本解码器,它被设计用来支持tcp的粘包和拆包问题。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty的TCP粘包拆包问题是由于底层的TCP协议无法理解上层的业务数据而导致的。为了解决这个问题Netty提供了几种解决方案。其,常用的解决方案有四种[1]: 1. 固定长度的拆包器(FixedLengthFrameDecoder):将每个应用层数据包拆分成固定长度的大小。这种拆包器适用于应用层数据包长度固定的情况。 2. 行拆包器(LineBasedFrameDecoder):将每个应用层数据包以换行符作为分隔符进行分割拆分。这种拆包器适用于应用层数据包以换行符作为结束符的情况。 3. 分隔符拆包器(DelimiterBasedFrameDecoder):将每个应用层数据包通过自定义的分隔符进行分割拆分。这种拆包器适用于应用层数据包以特定分隔符作为结束标志的情况。 4. 基于数据包长度的拆包器(LengthFieldBasedFrameDecoder):将应用层数据包的长度作为接收端应用层数据包的拆分依据。根据应用层协议包含的数据包长度进行拆包。这种拆包器适用于应用层协议包含数据包长度的情况。 除了使用这些拆包器,还可以根据业界主流协议的解决方案来解决粘包拆包问题[3]: 1. 消息长度固定:累计读取到长度和为定长LEN的报文后,就认为读取到了一个完整的信息。 2. 使用特殊的分隔符:将换行符或其他特殊的分隔符作为消息的结束标志。 3. 在消息头定义长度字段:通过在消息头定义长度字段来标识消息的总长度。 综上所述,Netty提供了多种解决方案来解决TCP粘包拆包问题,可以根据具体的业务需求选择合适的解决方案[1][3]。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值