标签:therefore could binary frame ref ring str 匹配 toc
如果您一直关注OneCoder,我们之前有两篇文章介绍关于Netty消息连续收发的问题。( 《Java NIO框架Netty教程(五) 消息收发次数不匹配的问题 》、《 Java NIO框架Netty教程(七)-再谈收发信息次数问题 》)。如果您经常的“怀疑”和思考,我们刚介绍过了Object的传递,您是否好奇,在Object传递中是否会有这样的问题?如果Object流的字节截断错乱,那肯定是会出错的。Netty一定不会这么傻的,那么Netty是怎么做的呢?
我们先通过代码验证一下是否有这样的问题。(有问题的可能性几乎没有。)
01./**02.* 当绑定到服务端的时候触发,给服务端发消息。03.*04.* @author lihzh05.* @alia OneCoder06.*/www.it165.net07.@Override08.public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {09.// 向服务端发送Object信息10.sendObject(e.getChannel());11.}12. 13./**14.* 发送Object15.*16.* @param channel17.* @author lihzh18.* @alia OneCoder19.*/20.private void sendObject(Channel channel) {21.Command command = new Command();22.command.setActionName("Hello action.");23.Command commandOne = new Command();24.commandOne.setActionName("Hello action. One");25.Command command2 = new Command();26.command2.setActionName("Hello action. Two");27.channel.write(command2);28.channel.write(command);29.channel.write(commandOne);30.}
打印结果:
Hello action. Two
Hello action.
Hello action. One
一切正常。那么Netty是怎么分割对象流的呢?看看ObjectDecoder怎么做的。
在ObjectDecoder的基类LengthFieldBasedFrameDecoder中注释中有详细的说明。我们这里主要介绍一下关键的代码逻辑:
01.@Override02.protected Object decode(03.ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {04. 05.if (discardingTooLongFrame) {06.long bytesToDiscard = this.bytesToDiscard;07.int localBytesToDiscard = (int) Math.min(bytesToDiscard, buffer.readableBytes());08.buffer.skipBytes(localBytesToDiscard);09.bytesToDiscard -= localBytesToDiscard;10.this.bytesToDiscard = bytesToDiscard;11.failIfNecessary(ctx, false);12.return null;13.}14. 15.if (buffer.readableBytes() < lengthFieldEndOffset) {16.return null;17.}18. 19.int actualLengthFieldOffset = buffer.readerIndex() + lengthFieldOffset;20.long frameLength;21.switch (lengthFieldLength) {22.case 1:23.frameLength = buffer.getUnsignedByte(actualLengthFieldOffset);24.break;25.case 2:26.frameLength = buffer.getUnsignedShort(actualLengthFieldOffset);27.break;28.case 3:29.frameLength = buffer.getUnsignedMedium(actualLengthFieldOffset);30.break;31.case 4:32.frameLength = buffer.getUnsignedInt(actualLengthFieldOffset);33.break;34.……
我们这里进入的是4,还记得在编码时候的开头的4位占位字节吗?跟踪进去发现。
1.public int getInt(int index) {2.return (array[index] & 0xff) << 24 |3.(array[index + 1] & 0xff) << 16 |4.(array[index + 2] & 0xff) << 8 |5.(array[index + 3] & 0xff) << 0;6.}
原来,当初在编码时,在流开头增加的4字节的字符是做这个的。他记录了当前了这个对象流的长度,便于在解码时候准确的计算出该对象流的长度,正确解码。看来,我们如果我们自己写的对象编码解码的工具,要考虑的还有很多啊。
附:LengthFieldBasedFrameDecoder的JavaDoc
001./**002.* A decoder that splits the received {@link ChannelBuffer}s dynamically by the003.* value of the length field in the message. It is particularly useful when you004.* decode a binary message which has an integer header field that represents the005.* length of the message body or the whole message.006.* <p>007.* {@link LengthFieldBasedFrameDecoder} has many configuration parameters so008.* that it can decode any message with a length field, which is often seen in009.* proprietary client-server protocols. Here are some example that will give010.* you the basic idea on which option does what.011.*012.* <h3>2 bytes length field at offset 0, do not strip header</h3>013.*014.* The value of the length field in this example is <tt>12 (0x0C)</tt> which015.* represents the length of "HELLO, WORLD". By default, the decoder assumes016.* that the length field represents the number of the bytes that follows the017.* length field. Therefore, it can be decoded with the simplistic parameter018.* combination.019.* <pre>020.* <b>lengthFieldOffset</b> = <b>0</b>021.* <b>lengthFieldLength</b> = <b>2</b>022.* lengthAdjustment = 0023.* initialBytesToStrip = 0 (= do not strip header)024.*025.* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)026.* +——–+—————-+ +——–+—————-+027.* | Length | Actual Content |—–>| Length | Actual Content |028.* | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |029.* +——–+—————-+ +——–+—————-+030.* </pre>031.*032.* <h3>2 bytes length field at offset 0, strip header</h3>033.*034.* Because we can get the length of the content by calling035.* {@link ChannelBuffer#readableBytes()}, you might want to strip the length036.* field by specifying <tt>initialBytesToStrip</tt>. In this example, we037.* specified <tt>2</tt>, that is same with the length of the length field, to038.* strip the first two bytes.039.* <pre>040.* lengthFieldOffset = 0041.* lengthFieldLength = 2042.* lengthAdjustment = 0043.* <b>initialBytesToStrip</b> = <b>2</b> (= the length of the Length field)044.*045.* BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)046.* +——–+—————-+ +—————-+047.* | Length | Actual Content |—–>| Actual Content |048.* | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |049.* +——–+—————-+ +—————-+050.* </pre>051.*052.* <h3>2 bytes length field at offset 0, do not strip header, the length field053.* represents the length of the whole message</h3>054.*055.* In most cases, the length field represents the length of the message body056.* only, as shown in the previous examples. However, in some protocols, the057.* length field represents the length of the whole message, including the058.* message header. In such a case, we specify a non-zero059.* <tt>lengthAdjustment</tt>. Because the length value in this example message060.* is always greater than the body length by <tt>2</tt>, we specify <tt>-2</tt>061.* as <tt>lengthAdjustment</tt> for compensation.062.* <pre>063.* lengthFieldOffset = 0064.* lengthFieldLength = 2065.* <b>lengthAdjustment</b> = <b>-2</b> (= the length of the Length field)066.* initialBytesToStrip = 0067.*068.* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)069.* +——–+—————-+ +——–+—————-+070.* | Length | Actual Content |—–>| Length | Actual Content |071.* | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |072.* +——–+—————-+ +——–+—————-+073.* </pre>074.*075.* <h3>3 bytes length field at the end of 5 bytes header, do not strip header</h3>076.*077.* The following message is a simple variation of the first example. An extra078.* header value is prepended to the message. <tt>lengthAdjustment</tt> is zero079.* again because the decoder always takes the length of the prepended data into080.* account during frame length calculation.081.* <pre>082.* <b>lengthFieldOffset</b> = <b>2</b> (= the length of Header 1)083.* <b>lengthFieldLength</b> = <b>3</b>084.* lengthAdjustment = 0085.* initialBytesToStrip = 0086.*087.* BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)088.* +———-+———-+—————-+ +———-+———-+—————-+089.* | Header 1 | Length | Actual Content |—–>| Header 1 | Length | Actual Content |090.* | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" |091.* +———-+———-+—————-+ +———-+———-+—————-+092.* </pre>093.*094.* <h3>3 bytes length field at the beginning of 5 bytes header, do not strip header</h3>095.*096.* This is an advanced example that shows the case where there is an extra097.* header between the length field and the message body. You have to specify a098.* positive <tt>lengthAdjustment</tt> so that the decoder counts the extra099.* header into the frame length calculation.100.* <pre>101.* lengthFieldOffset = 0102.* lengthFieldLength = 3103.* <b>lengthAdjustment</b> = <b>2</b> (= the length of Header 1)104.* initialBytesToStrip = 0105.*106.* BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)107.* +———-+———-+—————-+ +———-+———-+—————-+108.* | Length | Header 1 | Actual Content |—–>| Length | Header 1 | Actual Content |109.* | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |110.* +———-+———-+—————-+ +———-+———-+—————-+111.* </pre>112.*113.* <h3>2 bytes length field at offset 1 in the middle of 4 bytes header,114.* strip the first header field and the length field</h3>115.*116.* This is a combination of all the examples above. There are the prepended117.* header before the length field and the extra header after the length field.118.* The prepended header affects the <tt>lengthFieldOffset</tt> and the extra119.* header affects the <tt>lengthAdjustment</tt>. We also specified a non-zero120.* <tt>initialBytesToStrip</tt> to strip the length field and the prepended121.* header from the frame. If you don‘t want to strip the prepended header, you122.* could specify <tt>0</tt> for <tt>initialBytesToSkip</tt>.123.* <pre>124.* lengthFieldOffset = 1 (= the length of HDR1)125.* lengthFieldLength = 2126.* <b>lengthAdjustment</b> = <b>1</b> (= the length of HDR2)127.* <b>initialBytesToStrip</b> = <b>3</b> (= the length of HDR1 + LEN)128.*129.* BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)130.* +——+——–+——+—————-+ +——+—————-+131.* | HDR1 | Length | HDR2 | Actual Content |—–>| HDR2 | Actual Content |132.* | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |133.* +——+——–+——+—————-+ +——+—————-+134.* </pre>135.*136.* <h3>2 bytes length field at offset 1 in the middle of 4 bytes header,137.* strip the first header field and the length field, the length field138.* represents the length of the whole message</h3>139.*140.* Let‘s give another twist to the previous example. The only difference from141.* the previous example is that the length field represents the length of the142.* whole message instead of the message body, just like the third example.143.* We have to count the length of HDR1 and Length into <tt>lengthAdjustment</tt>.144.* Please note that we don‘t need to take the length of HDR2 into account145.* because the length field already includes the whole header length.146.* <pre>147.* lengthFieldOffset = 1148.* lengthFieldLength = 2149.* <b>lengthAdjustment</b> = <b>-3</b> (= the length of HDR1 + LEN, negative)150.* <b>initialBytesToStrip</b> = <b> 3</b>151.*152.* BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)153.* +——+——–+——+—————-+ +——+—————-+154.* | HDR1 | Length | HDR2 | Actual Content |—–>| HDR2 | Actual Content |155.* | 0xCA | 0×0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |156.* +——+——–+——+—————-+ +——+—————-+157.* </pre>158.*159.* @see LengthFieldPrepender160.*/
Java NIO框架Netty教程(十) Object对象的连续收发解析分析
标签:therefore could binary frame ref ring str 匹配 toc
原文地址:http://www.cnblogs.com/hashcoder/p/7648426.html