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,解码时自动去掉前面四个字节
相关文章推荐
- OC-封装、继承、多态
- OC类与对象
- OSChina 周四乱弹 —— “面向对象编程”没几个人懂
- Netty系列之Netty编解码框架分析
- socket write read 阻塞 非阻塞
- Allegro制作椭圆形flash的流程
- FFmpeg滤镜代码级分析
- OC基础学习
- 360广告联盟上线了
- 程序员的另一出路:大数据工程师
- Netty解决半包(TCP粘包/拆包导致)读写问题
- C语言学习总结
- C语言学习-指针
- 不只是看上去很美(第二弹:打造最美3D机房)
- Uplooking notes 03day
- Oracle查看和修改连接数
- linux下启动关闭oracle
- ERMaster在线安装
- vmware安装ubuntu时安装vmware tool解决方法,及使用鼠标滚轮的方法.
- Design Pattern Explained