您的位置:首页 > 其它

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

2015-07-09 07:02 513 查看

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

1.frame包整体功能描述

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





2.包中各类功能详解

(1)FrameDecoder

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

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

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

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

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

|AB|CDEFG|H|I|

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



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

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

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

|ABC|DEF|GHI|

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


如下是一个自定义的Decoder类

publicclassMyFrameDecoderextendsFrameDecoder{

@Override
protectedObjectdecode(ChannelHandlerContextctx,
channel,
ChannelBufferbuf)throwsException{

//Makesureifthelengthfieldwasreceived.
if(buf.readableBytes()<4){
//Thelengthfieldwasnotreceivedyet-returnnull.
//Thismethodwillbeinvokedagainwhenmorepacketsare
//receivedandappendedtothebuffer.
returnnull;
}

//Thelengthfieldisinthebuffer.

//Markthecurrentbufferpositionbeforereadingthelengthfield
//becausethewholeframemightnotbeinthebufferyet.
//Wewillresetthebufferpositiontothemarkedpositionif
//there'snotenoughbytesinthebuffer.
buf.markReaderIndex();


//Readthelengthfield.
intlength=buf.readInt();


//Makesureifthere'senoughbytesinthebuffer.
if(buf.readableBytes()<length){

//Thewholebyteswerenotreceivedyet-returnnull.
//Thismethodwillbeinvokedagainwhenmorepacketsare
//receivedandappendedtothebuffer.
//Resettothemarkedpositiontoreadthelengthfieldagain
//nexttime.
buf.resetReaderIndex();


returnnull;

}

//There'senoughbytesinthebuffer.Readit.
ChannelBufferframe=buf.readBytes(length);


//Successfullydecodedaframe.Returnthedecodedframe.
returnframe;

}
}

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

(2)FixedLengthFrameDecoder

FixedLengthFrameDecoder主要是将诸如
+----+-------+---+---+

|AB|CDEFG|H|I|

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


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

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

|ABC|DEF|GHI|

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


构造方法为:newFixedLengthFrameDecoder(intframeLength);

frameLength即修正后的帧长度

另一个构造方法为newFixedLengthFrameDecoder(intframeLength,booleanallocateFullBuffer);

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


(3)Delimiters

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

对FlashXML的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.2byteslengthfieldatoffset0,donotstripheader



lengthFieldOffset=0

lengthFieldLength=2

lengthAdjustment=0

initialBytesToStrip=0(=donotstripheader)






BEFOREDECODE(14bytes)AFTERDECODE(14bytes)


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

|Length|ActualContent|---->|Length|ActualContent|

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

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



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



2.2byteslengthfieldatoffset0,stripheader



lengthFieldOffset=0

lengthFieldLength=2

lengthAdjustment=0

initialBytesToStrip=2(=thelengthoftheLengthfield)


BEFOREDECODE(14bytes)AFTERDECODE(12bytes)

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

|Length|ActualContent|---->|ActualContent|

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

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




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



3.2byteslengthfieldatoffset0,donotstripheader,thelengthfieldrepresentsthelengthofthewholemessage



lengthFieldOffset=0

lengthFieldLength=2

lengthAdjustment=-2(=thelengthoftheLengthfield)

initialBytesToStrip=0


BEFOREDECODE(14bytes)AFTERDECODE(14bytes)

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

|Length|ActualContent|---->|Length|ActualContent|

|0x000E|"HELLO,WORLD"||0x000E|"HELLO,WORLD"|

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



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



4.3byteslengthfieldattheendof5bytesheader,donotstripheader

lengthFieldOffset=2(=thelengthofHeader1)

lengthFieldLength=3

lengthAdjustment=0

initialBytesToStrip=0


BEFOREDECODE(17bytes)AFTERDECODE(17bytes)

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

|Header1|Length|ActualContent|--->|Header1|Length|ActualContent|

|0xCAFE|0x00000C|"HELLO,WORLD"||0xCAFE|0x00000C|"HELLO,WORLD"|

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




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

4.3byteslengthfieldatthebeginningof5bytesheader,donotstripheader

lengthFieldOffset=0

lengthFieldLength=3

lengthAdjustment=2(=thelengthofHeader1)

initialBytesToStrip=0


BEFOREDECODE(17bytes)AFTERDECODE(17bytes)

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

|Length|Header1|ActualContent|----->|Length|Header1|ActualContent|

|0x00000C|0xCAFE|"HELLO,WORLD"||0x00000C|0xCAFE|"HELLO,WORLD"|

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


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

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



5.2byteslengthfieldatoffset1inthemiddleof4bytesheader,stripthefirstheaderfieldandthelengthfield





lengthFieldOffset=1(=thelengthofHDR1)

lengthFieldLength=2

lengthAdjustment=1(=thelengthofHDR2)

initialBytesToStrip=3(=thelengthofHDR1+LEN)



BEFOREDECODE(16bytes)AFTERDECODE(13bytes)

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

|HDR1|Length|HDR2|ActualContent|----->|HDR2|ActualContent|

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

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



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



6.2byteslengthfieldatoffset1inthemiddleof4bytesheader,stripthefirstheaderfieldandthelengthfield,thelengthfieldrepresentsthelengthof
thewholemessage



lengthFieldOffset=1

lengthFieldLength=2

lengthAdjustment=-3(=thelengthofHDR1+LEN,negative)

initialBytesToStrip=3



BEFOREDECODE(16bytes)AFTERDECODE(13bytes)

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

|HDR1|Length|HDR2|ActualContent|----->|HDR2|ActualContent|

|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"|

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

格式的数据



应用场景:自定义pipelineFactory类:MyPipelineFactoryimplementsChannelPipelineFactory



pipeline.addLast("frameEncode",newLengthFieldPrepender(4,false));


(7)TooLongFrameException

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


(8)CorruptedFrameException

定义的数据包损坏异常类


3.frame包应用demo

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



publicclassMyPipelineFactoryimplementsChannelPipelineFactory{

@Override

publicChannelPipelinegetPipeline()throwsException{

ChannelPipelinepipeline=Channels.pipeline();

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

pipeline.addLast("encoder",newLengthFieldPrepender(4,false));

pipeline.addLast("handler",newMyHandler());

returnpipeline;

}

}

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


pipeline.addLast("handler",newMyHandler());

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

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


真实数据最大字节数为Integer.MAX_VALUE,解码时自动去掉前面四个字节
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: