netty 数据分包、组包、粘包处理机制

转载 2015年07月09日 07:02:45

转载自 断鸿零雁 blog.163.com/linfenliang@126

1.            frame包整体功能描述

此包主要作用于对TCP/IP数据包的分包和包重组,常用于数据的流传输,是扩展的解码器。

包目录结构如下:

netty 数据分包、组包、粘包处理机制(一) - 断鸿零雁 - 断鸿零雁的博客
 

 

2.            包中各类功能详解

(1)  FrameDecoder

抽象类,将ChannelBuffers中的二进制数据转换成有意义的数据帧(frame)对象,一般不直接调用,提供给此包中的FixedLengthFrameDecoder类、DelimiterBasedFrameDecoder类和LengthFieldBasedFrameDecoder类使用,也可以提供给其他类使用(暂不探讨);

 

在数据传输中,我们发送的数据包如下所示

 

+-----+-----+-----+

 | ABC | DEF | GHI |

 +-----+-----+-----+

 

而实际接收的包的格式为:

 

+----+-------+---+---+
 | AB | CDEFG | H | I |
 +----+-------+---+---+

 

 

产生的原因为:数据在传输过程中,产生数据包碎片(TCP/IP数据传输时大数据包无法一次传输,被拆分成小数据包,小数据包即为数据包碎片),这就造成了实际接收的数据包和发送的数据包不一致的情况。

 

而通过FrameDecoder即可实现对上述接收到的数据包的整理,重新还原成如下格式:

 

+-----+-----+-----+
 | ABC | DEF | GHI |
 +-----+-----+-----+

 

如下是一个自定义的Decoder类

 

public class MyFrameDecoder extends FrameDecoder {

 

         @Override

   protected Object decode(ChannelHandlerContext ctx,

                           channel,

                           ChannelBuffer buf) throws Exception {

 

     // Make sure if the length field was received.

     if (buf.readableBytes() < 4) {

        // The length field was not received yet - return null.

        // This method will be invoked again when more packets are

        // received and appended to the buffer.

        return null;

     }

 

     // The length field is in the buffer.

 

     // Mark the current buffer position before reading the length field

     // because the whole frame might not be in the buffer yet.

     // We will reset the buffer position to the marked position if

     // there's not enough bytes in the buffer.

     buf.markReaderIndex();
 

     // Read the length field.

     int length = buf.readInt();
 

     // Make sure if there's enough bytes in the buffer.

     if (buf.readableBytes() < length) {

        // The whole bytes were not received yet - return null.

        // This method will be invoked again when more packets are

        // received and appended to the buffer.

        // Reset to the marked position to read the length field again

        // next time.

        buf.resetReaderIndex();
 
        return null;

     }

 

     // There's enough bytes in the buffer. Read it.

     ChannelBuffer frame = buf.readBytes(length);
 

     // Successfully decoded a frame.  Return the decoded frame.

     return frame;

   }

 }

 

此时,我们无需关注数据包是如何重组的,只需要做简单的验证(按照一个包验证)就可以了,FrameDecoder内部实现了组包的机制,不过,此时,需在数据的最前面封装整个数据的长度,示例中数据长度占了四个字节,即前四个字节是数据长度,后面的才是真实的数据。

(2)  FixedLengthFrameDecoder

FixedLengthFrameDecoder主要是将诸如

+----+-------+---+---+
 | AB | CDEFG | H | I |
 +----+-------+---+---+

 

此类的数据包按照指定的frame长度重新组包,比如确定长度为3,则组包为

 

+-----+-----+-----+
 | ABC | DEF | GHI |
 +-----+-----+-----+

 

构造方法为:new FixedLengthFrameDecoder(int frameLength);

 

frameLength即修正后的帧长度

 

另一个构造方法为new FixedLengthFrameDecoder(int frameLength, boolean allocateFullBuffer);

 

allocateFullBuffer如果为真,则表示初始化的ChannelBuffer大小为frameLength。

 

 

(3)  Delimiters

分隔符类,DelimiterBasedFrameDecoder类的辅助类。

 

对Flash XML的socket通信采用nulDelimiter()方法,对于一般的文本采用lineDelimiter()方法

 

(4)  DelimiterBasedFrameDecoder

对接收到的ChannelBuffers按照指定的分隔符Delimiter分隔,分隔符可以是一个或者多个

 

如将以下数据包按照“\n”分隔:

 
+--------------+
 | ABC\nDEF\r\n |
 +--------------+

 

即为:

 

+-----+-----+
 | ABC | DEF |
 +-----+-----+
 

而如果按照“\r\n”分隔,则为:

 

+----------+
 | ABC\nDEF |
 +----------+

 

对于DelimiterBasedFrameDecoder中的构造方法,其中一些参数说明:

 

maxFrameLength:解码的帧的最大长度

stripDelimiter:解码时是否去掉分隔符

failFast:为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常

delimiter:分隔符


(5)  LengthFieldBasedFrameDecoder

常用的处理大数据分包传输问题的解决类,先对构造方法LengthFieldBasedFrameDecoder中的参数做以下解释说明“

maxFrameLength:解码的帧的最大长度

lengthFieldOffset :长度属性的起始位(偏移位),包中存放有整个大数据包长度的字节,这段字节的其实位置

lengthFieldLength:长度属性的长度,即存放整个大数据包长度的字节所占的长度

lengthAdjustmen:长度调节值,在总长被定义为包含包头长度时,修正信息长度。initialBytesToStrip:跳过的字节数,根据需要我们跳过lengthFieldLength个字节,以便接收端直接接受到不含长度属性的内容

failFast :为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常

 

下面对各种情况分别描述:

 

1. 2 bytes length field at offset 0, do not strip header

 

lengthFieldOffset   = 0

 lengthFieldLength   = 2

 lengthAdjustment    = 0

 initialBytesToStrip = 0 (= do not strip header)

 

 

 

 BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)

+--------+----------------+       +--------+----------------+

 | Length | Actual Content |---->| Length | Actual Content |

 | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |

 +--------+----------------+      +--------+----------------+

 

此时数据格式不做任何改变(没有跳过任何字节)

 

2. 2 bytes length field at offset 0, strip header

 

lengthFieldOffset   = 0

 lengthFieldLength   = 2

 lengthAdjustment    = 0

 initialBytesToStrip = 2 (= the length of the Length field)

 

 BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
 +--------+----------------+      +----------------+
 | Length | Actual Content |---->| Actual Content |
 | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
 +--------+----------------+      +----------------+

 

此时帧长度为14个字节,但由于lengthFieldOffset = 0两个lengthFieldLength = 2)字节是表示帧长度的字节,不计入数据,故真实的数据长度为12个字节。

 

3. 2 bytes length field at offset 0, do not strip header, the length field represents the length of the whole message

 

lengthFieldOffset   =  0

 lengthFieldLength   =  2

 lengthAdjustment    = -2 (= the length of the Length field)

 initialBytesToStrip =  0

 

 BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
 +--------+----------------+     +--------+----------------+
 | Length | Actual Content |---->| Length | Actual Content |
 | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
 +--------+----------------+     +--------+----------------+
 

此处定义的Length0x000E共占了两个字节,表示的帧长度为14个字节,lengthFieldOffset = 0两个lengthFieldLength = 2)字节为Length,由于设置的lengthAdjustment    = -2 (= the length of the Length field),故修正的信息实际长度补2,即解码时往前推2个字节,解码后还是14个字节长度(此种情况是把整个长度封装,一般来讲,我们只封装数据长度)

 

4. 3 bytes length field at the end of 5 bytes header, do not strip header

lengthFieldOffset   = 2 (= the length of Header 1)

 lengthFieldLength   = 3

 lengthAdjustment    = 0

 initialBytesToStrip = 0

 

BEFORE DECODE (17 bytes)                 AFTER DECODE (17 bytes)
 +---------+---------+--------------+    +---------+---------+------------+
 | Header 1| Length  |Actual Content|--->| Header 1| Length | Actual Content|
 |  0xCAFE | 0x00000C|"HELLO, WORLD"|    |  0xCAFE   |0x00000C| "HELLO, WORLD"|
 +---------+---------+--------------+    +----------+--------+-----------+

 

此处lengthFieldOffset   = 2,从第3个字节开始表示数据长度,长度占3个字节,真实数据长度为0x00000C 12个字节,而lengthAdjustment=0initialBytesToStrip = 0,故解码后的数据与解码前的数据相同。

4. 3 bytes length field at the beginning of 5 bytes header, do not strip header

lengthFieldOffset   = 0

 lengthFieldLength   = 3

 lengthAdjustment    = 2 (= the length of Header 1)

 initialBytesToStrip = 0

 

BEFORE DECODE (17 bytes)                          AFTER DECODE (17 bytes)
 +----------+----------+----------------+      +----------+----------+----------------+
 |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
 | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
 +----------+----------+----------------+      +----------+----------+----------------+

此处由于修正的字节数是2,且initialBytesToStrip = 0,故整个数据的解码数据保持不变

总字节数是17,开始的三个字节表示字节长度:12,修正的字节是2,(即从第三个字节开始,再加两个开始是真正的数据,其中跳过的字节数是0

 

5. 2 bytes length field at offset 1 in the middle of 4 bytes header, strip the first header field and the length field

 

 

lengthFieldOffset   = 1 (= the length of HDR1)

 lengthFieldLength   = 2

 lengthAdjustment    = 1 (= the length of HDR2)

 initialBytesToStrip = 3 (= the length of HDR1 + LEN)

 

BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)

 +------+--------+------+----------------+      +------+----------------+

 | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |

 | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

 +------+--------+------+----------------+      +------+----------------+

 

从第2个字节开始解码,取两个字节作为帧长度,为12个字节,然后,修正一个字节,从第5个字节到最后表示帧数据,解码时,由于initialBytesToStrip=3,表示跳过前三个字节(去掉),故从第四个字节开始解析,解析出来后,如右图所示。

 

6. 2 bytes length field at offset 1 in the middle of 4 bytes header, strip the first header field and the length field, the length field represents the length of the whole message

 

lengthFieldOffset   =  1

 lengthFieldLength   =  2

 lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)

 initialBytesToStrip =  3

 

BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)

 +------+--------+------+----------------+      +------+----------------+

 | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |

 | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

 +------+--------+------+----------------+      +------+----------------+

 

从第二个字节开始,取两个字节作为帧长度,为16个字节,然后补3个字节,故往前找三个字节,从HDP1开始解码,而又因为initialBytesToStrip=3,解码时忽略掉前三个字节,故从第四个字节开始解析,解析结果如右图所示。

 

总结:一般来讲,当lengthAdjustment 为负数时,Length表示的是整个帧的长度,当lengthAdjustment为正数或0时,表示真实数据长度。

(6)  LengthFieldPrepender

编码类,自动将

+----------------+

| "HELLO, WORLD" |

+----------------+

格式的数据转换成

+--------+----------------+

 + 0x000C | "HELLO, WORLD" |

 +--------+----------------+

格式的数据,

如果lengthIncludesLengthFieldLength设置为true,则编码为

+--------+----------------+

+ 0x000E | "HELLO, WORLD" |

+--------+----------------+

格式的数据

 

应用场景:自定义pipelineFactoryMyPipelineFactory implements ChannelPipelineFactory

pipeline.addLast("frameEncode", new LengthFieldPrepender(4, false));

(7)  TooLongFrameException

定义的数据包超过预定义大小异常类

(8)  CorruptedFrameException

定义的数据包损坏异常类

3.            frame包应用demo

解决分包问题,通常配置MyPipelineFactory中设置,示例如下:

 

public class MyPipelineFactory implements ChannelPipelineFactory {

 

    @Override

    public ChannelPipeline getPipeline() throws Exception {

       ChannelPipeline pipeline = Channels.pipeline();

       pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); 

        pipeline.addLast("encoder", new LengthFieldPrepender(4, false));

       pipeline.addLast("handler", new MyHandler());

       return pipeline;

    }

 

}

 

 

在客户端设置pipeline.addLast("encoder", new LengthFieldPrepender(4, false));

       pipeline.addLast("handler", new MyHandler());

 

前四个字节表示真实的发送的数据长度Length,编码时会自动加上;

 

在服务器端设置pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));

真实数据最大字节数为Integer.MAX_VALUE,解码时自动去掉前面四个字节


相关文章推荐

实例:Netty 处理 TCP协议数据分包问题

我们知道通过TCP协议发送接收数据时,如果数据过大,接受到的数据会是分包的,比如:                              ...

Netty数据包大小的确定

Netty接收包大小的确定 技术点描述 关于NETTY内部TCP、UDP数据包问题 当客户端数据量过大时,TCP协议会自动分包进行数据传输(何时分包,如何分包,每包大小尚未研究), 使用netty做s...

netty 数据分包、组包、粘包处理机制(二)

(5)  LengthFieldBasedFrameDecoder常用的处理大数据分包传输问题的解决类,先对构造方法LengthFieldBasedFrameDecoder中的参数做以下解释说明“ma...

Netty4 自定义Decoder,Encoder进行对象传递(粘包处理)

首先我们必须知道Tcp粘包和拆包的,TCP是个“流”协议,所谓流,就是没有界限的一串数据,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际数据进行包的划分,一个完整的包可能会被拆分...

TCP三次握手协议

三次握手针对于TCP的连接(因TCP连接是可靠的连接)而采用的。 名词解释: 针对本文中的用到的一些名词,作如下解释: SYN包:同步序列号,他是TCP连接的第一个数据包,非常小,此包表明发送方...

Top 20 Programming Lessons I\'ve Learned in 20 Years

I've been programming since I was 11 and I've loved technology and programming every since. There ar...

Where is the “Father of Java?”

This article was originally posted by Tyler Hamilton in the Toronto Star. It isn’t often that we rem...

socket的半包,粘包与分包的问题

http://zhaohuiopensource.iteye.com/blog/1541270 首先看两个概念:  短连接:  连接->传输数据->关闭连接     HTTP是无状态的,浏...

Qt通过UDP传图片,实现自定义分包和组包

一.包头结构体 //包头 struct PackageHeader { //包头大小(sizeof(PackageHeader)) unsigned int uTransPack...

SOCKET通信中TCP、UDP数据包大小的确定

转载:http://blog.csdn.net/yaopeng_2005/article/details/6706739 TCP、UDP数据包大小的确定     UDP和TCP...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)