innettynetty中的数据是通过ByteBuf传输的。一个ByteBuf可能包含多个有意义的数据。这些数据可以称为frame,也就是说ByteBuf中可以包含一个MultipleFrames。对于消息的接收者来说,接收到ByteBuf后,需要从ByteBuf中解析出有用的数据,然后对ByteBuf中的帧进行拆分解析。一般来说,不同的帧之间会有一些特定的分隔符,我们可以通过这些分隔符来区分帧,从而实现对数据的分析。Netty为我们提供了一些合适的帧解码器,使用这些帧解码器可以有效的简化我们的工作。下图是netty中几种常见的帧解码器:/>接下来,我们就详细介绍一下以上帧解码器的使用。LineBasedFrameDecoderLineBasedFrameDecoder从名字上看就是以行来区分帧。根据操作系统的不同,换行符可以有两个换行符,“\n”和“\r\n”。LineBasedFrameDecoder的基本原理是从ByteBuf中读取相应的字符来匹配“\n”和“\r\n”,从而实现字符的准确比较。这些frameDecoder对于字符编码也有一定的要求。一般来说,需要UTF-8编码。因为在这种编码中,“\n”和“\r”作为一个字节出现,不会在其他组合编码中使用,所以使用“\n”和“\r”来判断安全非常重要。LineBasedFrameDecoder中有几个重要的属性。一个是maxLength属性,用于检测接收到的消息的长度。如果超过长度限制,将抛出TooLongFrameException。还有一个stripDelimiter属性,用来判断是否需要过滤掉分隔符。还有failFast。如果该值为true,无论是否读取帧,只要帧的长度超过maxFrameLength,就会抛出TooLongFrameException。如果值为false,则在完整读取整个帧后将抛出TooLongFrameException。LineBasedFrameDecoder的核心逻辑是先找到行分隔符的位置,然后根据这个位置读取对应的帧信息。下面是查找行分隔符的findEndOfLine方法:privateintfindEndOfLine(finalByteBufbuffer){inttotalLength=buffer.readableBytes();inti=buffer.forEachByte(buffer.readerIndex()+offset,totalLength-offset,ByteProcessor.FIND_LF);if(i>=0){偏移量=0;if(i>0&&buffer.getByte(i-1)=='\r'){i--;}}else{offset=totalLength;}返回我;}这里使用了一个ByteBuf的forEachByte来遍历ByteBuf。我们要找的字符是:ByteProcessor.FIND_LF。最后,LineBasedFrameDecoder解码出来的对象还是一个ByteBuf。DelimiterBasedFrameDecoder上面提到的LineBasedFrameDecoder只对行分隔符有效。如果我们的frame被其他分隔符分割了,LineBasedFrameDecoder就不起作用了,所以netty提供了一个更通用的DelimiterBasedFrameDecoder。这个frameDecoder可以自定义分隔符:}传入的分隔符是一个ByteBuf,所以分隔符可能不止一个字符。为了解决这个问题,在DelimiterBasedFrameDecoder中定义了一个ByteBuf数组:privatefinalByteBuf[]delimiters;分隔符=delimiter.readableBytes();这个delimiters是调用delimiter的readableBytes得到的。DelimiterBasedFrameDecoder的逻辑与LineBasedFrameDecoder类似。它通过比较缓冲区中的字符截获缓冲区中的数据。但是,DelimiterBasedFrameDecoder可以接受多个定界符,因此它的用处将非常广泛。FixedLengthFrameDecoder除了通过比较ByteBuf中的字符进行分帧外,还有其他一些常用的分帧方式,比如按照特定的长度来区分。Netty提供了这样一个解码器,叫做FixedLengthFrameDecoder。publicclassFixedLengthFrameDecoderextendsByteToMessageDecoderFixedLengthFrameDecoder也是继承自ByteToMessageDecoder,它的定义很简单,传入一帧的长度即可:然后调用this.frameByteBuf的readRetainedSlice方法读取定长数据:in.readRetainedSlice(frameLength)最终返回读取到的数据。LengthFieldBasedFrameDecoder也有一些包含特定长度字段的帧。该长度字段表示ByteBuf中有多少可读数据。这样的框架称为LengthFieldBasedFrame。Netty也提供了相应的处理解码器:publicclassLengthFieldBasedFrameDecoderextendsByteToMessageDecoder读取的逻辑很简单,先读取长度,再根据长度读取数据。为了实现这个逻辑,LengthFieldBasedFrameDecoder提供了4个字段,分别是lengthFieldOffset、lengthFieldLength、lengthAdjustment和initialBytesToStrip。lengthFieldOffset指定长度域的起始位置,lengthFieldLength定义长度域的长度,lengthAdjustment调整lengthFieldLength,initialBytesToStrip表示是否需要移除长度域。听起来很难理解,举几个例子吧,第一个是最简单的:BEFOREDECODE(14bytes)A??FTERDECODE(14bytes)+--------+----------------++--------+----------------+|长度|实际内容|----->||实际内容||0x000C|“你好,世界”||0x000C|------+----------------+待编码的报文有一个长度域,长度域之后是真正的数据,0x000C是一个十六进制,代表的数据是12,也就是“HELLO,WORLD”中字符串的长度。这里四个属性的值分别是:lengthFieldOffset=0lengthFieldLength=2lengthAdjustment=0initialBytesToStrip=0表示长度字段从0开始,长度字段占两个字节,长度不需要调整,并且该字段不需要调整。让我们看一个更复杂的例子。本例中四个属性值如下:lengthFieldOffset=1lengthFieldLength=2lengthAdjustment=1initialBytesToStrip=3对应的编码数据如下:BEFOREDECODE(16bytes)A??FTERDECODE(13bytes)+------+--------+------+----------------++------+---------------+|HDR1|长度|HDR2|实际内容|----->|HDR2|实际内容||0xCA|0x000C|0xFE|“你好,世界”||0xFE|“你好,世界”|+------+--------+------+------------++------+---------------+上例中,长度字段从第1字节开始(第0字节为HDR1),长度字段占2字节,长度调整1字节。最终数据的起始位置为1+2+1=4,然后截取前3个字节的数据,得到最终结果。综上所述,netty提供的这些基于字符集的帧解码器,基本可以满足我们日常的工作需要。当然,如果你是传输一些比较复杂的对象,可以考虑自定义编码和解码。只要保持自定义逻辑步骤与我们上面解释的一致即可。本文已收录于http://www.flydean.com/14-5-netty-frame-decoder/最流行的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!
