NIO消息黏包和半包处理

1、前言

我们在进行NIO编程时,通常会使用缓冲区进行消息的通信(ByteBuffer),而缓冲区的大小是固定的。那么除非你进行自动扩容(Netty就是这么处理的),否则的话,当你的消息存进该缓冲区就会存在消息边界的问题,典型的边界问题就是黏包和半包现象。

2、什么是消息黏包?

当ByteBuffer设置足够大时,会有多条消息从channel写进ByteBuffer,这时候就无法愤青数据包的边界,所有数据包粘连在一起,称为黏包问题。

如:

NIO消息黏包和半包处理_第1张图片

3、什么是消息半包?

当数据包足够大,ByteBuffer直接被填满,但是又不包含完整的数据包。这就会导致从缓冲区中取出的消息不完整,有点像消息被“砍了一半”,称为半包问题。

如:

NIO消息黏包和半包处理_第2张图片

4、三种解决思路

4.1、固定缓冲区和数据包大小

固定缓冲区和数据包大小,顾名思义就是服务端按照预定的长度读取。数据包发送的大小和ByteBuffer固定大小填充传输,就算数据包小于ByteBuffer容量,也需要填充满。

如:

NIO消息黏包和半包处理_第3张图片

很明显这种方案的缺点就是浪费带宽。因为如果数据包有多大,就算只有1字节,剩下的也需要用多余的数据填充。

4.2、按分隔符拆分不同缓冲区

按既定的分隔符拆分(如\r,\n)。缓冲区读取按既定分隔符截取,依次判断如果是分隔符,就创建相应缓冲区进行存储。保证了分隔符前后数据不会冲突。

如:

NIO消息黏包和半包处理_第4张图片 

很明显这种方案有个致命问题,就是效率低。每分割一条消息就需要创建自动扩容的ByteBuffer。

参考代码:

private static void split(final ByteBuffer buffer) {
    buffer.flip();
    for(int i=0;i

4.3、报文头添加消息长度字段

这种方案也是最常用的方案,就是在传输的报文头添加一个固定长度的字段,用来存储当前这条消息具体数据的长度。这样当我们接收到这条报文之后,只要固定解析报文头部几个字节,就可以知道当前这条消息的长度,然后进而进行解析。

这也就是TLV格式,即 Type 类型、Length 长度、Value 数据(也就是在消息开头用一些空间存放后面数据的长度),如HTTP请求头中的Content-Type与Content-Length。类型和长度已知的情况下,就可以方便获取消息大小,分配合适的 buffer,缺点是 buffer 需要提前分配,如果内容过大,则影响 server 吞吐量。

  • Http 1.1 是TLV格式
  • Http 2.0 是LTV格式

如以下的http请求响应头,便可以看到Content-Length:121。这就是消息具体数据的长度。

NIO消息黏包和半包处理_第5张图片

 

如:

NIO消息黏包和半包处理_第6张图片

 或

NIO消息黏包和半包处理_第7张图片

 

你可能感兴趣的:(java,netty,nio,java,网络)