likes
comments
collection
share

Netty高级应用之粘包和拆包

作者站长头像
站长
· 阅读数 12

1、什么是粘包和拆包

Netty高级应用之粘包和拆包 比如我们现在要发送二条消息给服务端,客户端发送了二次,那么理论上服务端也应该接收到二次消息,而且二次的消息内容分别是 ABC / DEF,但是这是不确定的,服务端会有以下几种情况

  • 1、有可能服务端只接收到一次消息并且消息内容是:ABCDEF
  • 2、服务端接收到二次消息,但是第一次消息是不完整的,第一次接收到的消息应该是ABC,但是有可能接收到的是A或者是AB,也就是第一次的消息内容会出现在第二次消息中
  • 3、服务端接收到二次消息,但是第二次消息是不完整的,第一次接收到的消息应该是ABC,但是有可能接收到的是ABCD或者是ABCDE,也就是第二次的消息内容会出现在第一次消息中

粘包:服务端一次性收到多条消息,理论上是客户端发送一条消息服务端接收一条,但是客户端将几条消息一次性发送过来,导致服务端一次性接收到多条消息,这就是粘包

Netty高级应用之粘包和拆包

客户端发送10条消息 Netty高级应用之粘包和拆包

服务端只接收到一次,客户端将10条消息合并发送过来了,这就是粘包 Netty高级应用之粘包和拆包

拆包:一条消息分开几次发送,比如我要发送:ABC,第一次服务端接收到了AB,第二次接收到了C,一条消息服务端要分好几次接收,这就是拆包

客户端发送的消息很大很大 Netty高级应用之粘包和拆包

服务端需要分开多次接收,这就是拆包,一条消息分开多次发送 Netty高级应用之粘包和拆包

2、粘包和拆包产生原因

无论是Netty还是NIO,发送消息都是基于TCP协议的。TCP发送消息的时候时候缓冲区的,发送消息基于缓冲区的要比基于的性能要好

  • 基于流的发送消息:比如我现在要发送5条消息,分别是:A,B,C,D,E,那么基于流的就要发送五次,但是如果是缓冲区,这五条消息有可能只需要发送一次就行,那么传输性能就会好上很多。举个生活的例子:搬砖,现在有10块砖(10条消息),基于流的就要一块一块的搬,我们要搬10次,但是基于缓冲区就类似我们就可以一次性的搬走,二者之间的效率就差了好多

粘包产生原因:消息内容很小,远远小于缓冲区的长度,这时候缓冲区是不会立马把这条消息发送出去的,而是跟其它消息一起发送出去。这样合并发送有利于提高效率,比如我要发送五条消息,消息内容分别是:N,E,T,T,Y,缓冲区最后有可能将这五条消息一次性的发送出去,而不会分开五次发

Netty高级应用之粘包和拆包

拆包产生的原因:如果这条消息很大,大到缓冲区都装不下,这时候这条消息就必须要分开多次进行发送了,这就好比我们火车运货一样,车子的容量就那么多,当东西都装不下的时候,我们只能分开几次运

3、如何解决粘包和拆包

其实粘包和拆包的本质是因为TCP是流式协议,而消息是无边界的,简单来说服务器是分辨不了这条消息是否是完整的一条消息,有可能这条消息是多条消息合并之后发送过来的,有可能这条消息分了多次发送。如果我们能解决消息边界的问题,那自然就能解决粘包和半包的问题了。所以常见的有:定长法,分隔符法,长度+内容法

Netty也提供了这四种的实现:

  • 定长法:FixedLengthFrameDecode,就是每个数据包都拆分成固定长度

  • 分隔符法:DelimiterBasedFrameDecode,每个数据包都根据自定义的分隔符进行拆分

  • 长度+内容法:LengthFieldBasedFrameDecode,将应用层数据包的长度,作为接收端应用层数据包的拆分依据。按照应用层数据包的大小,拆包。这个拆包器,有一个要求,就是应用层协议中包含数据包的长度

  • 行拆包器:LineBasedFrameDecoder,每个应用层数据包,都以换行符作为分隔符,进行分割拆分

LineBasedFrameDecoder例子:

//添加行拆包处理器
pipeline.addLast(new LineBasedFrameDecoder(1024));

//发送消息
ctx.writeAndFlush(Unpooled.copiedBuffer("你好呀,我是Netty客户端"+i+"\n",CharsetUtil.UTF_8));

DelimiterBasedFrameDecode例子:

//添加自定义分隔符处理器
ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8));
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(2048, byteBuf));

//发送消息
ctx.writeAndFlush(Unpooled.copiedBuffer("你好呀,我是Netty客户端"+i+"$",CharsetUtil.UTF_8));