• 热门专题

Java NIO框架Netty教程(十) Object对象的连续收发解析分析

作者:One Coder  发布日期:2012-07-27 14:09:00
Tag标签:NIO框架  Netty教程  Object对象  
  • 如果您一直关注OneCoder,我们之前有两篇文章介绍关于Netty消息连续收发的问题。( 《Java NIO框架Netty教程(五) 消息收发次数不匹配的问题  》、《 Java NIO框架Netty教程(七)-再谈收发信息次数问题  》)。如果您经常的“怀疑”和思考,我们刚介绍过了Object的传递,您是否好奇,在Object传递中是否会有这样的问题?如果Object流的字节截断错乱,那肯定是会出错的。Netty一定不会这么傻的,那么Netty是怎么做的呢?

    我们先通过代码验证一下是否有这样的问题。(有问题的可能性几乎没有。)

     

    /**
    	 * 当绑定到服务端的时候触发,给服务端发消息。
    	 * 
    	 * @author lihzh
    	 * @alia OneCoder
    	 */www.it165.net
    	@Override
    	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    		// 向服务端发送Object信息
    		sendObject(e.getChannel());
    	}
    
    	/**
    	 * 发送Object
    	 * 
    	 * @param channel
    	 * @author lihzh
    	 * @alia OneCoder
    	 */
    	private void sendObject(Channel channel) {
    		Command command = new Command();
    		command.setActionName("Hello action.");
    		Command commandOne = new Command();
    		commandOne.setActionName("Hello action. One");
    		Command command2 = new Command();
    		command2.setActionName("Hello action. Two");
    		channel.write(command2);
    		channel.write(command);
    		channel.write(commandOne);
    	}
    

    打印结果:

    Hello action. Two
    Hello action.
    Hello action. One

    一切正常。那么Netty是怎么分割对象流的呢?看看ObjectDecoder怎么做的。
    在ObjectDecoder的基类LengthFieldBasedFrameDecoder中注释中有详细的说明。我们这里主要介绍一下关键的代码逻辑:

     

    @Override
        protected Object decode(
                ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
    
            if (discardingTooLongFrame) {
                long bytesToDiscard = this.bytesToDiscard;
                int localBytesToDiscard = (int) Math.min(bytesToDiscard, buffer.readableBytes());
                buffer.skipBytes(localBytesToDiscard);
                bytesToDiscard -= localBytesToDiscard;
                this.bytesToDiscard = bytesToDiscard;
                failIfNecessary(ctx, false);
                return null;
            }
    
            if (buffer.readableBytes() < lengthFieldEndOffset) {
                return null;
            }
    
            int actualLengthFieldOffset = buffer.readerIndex() + lengthFieldOffset;
            long frameLength;
            switch (lengthFieldLength) {
            case 1:
                frameLength = buffer.getUnsignedByte(actualLengthFieldOffset);
                break;
            case 2:
                frameLength = buffer.getUnsignedShort(actualLengthFieldOffset);
                break;
            case 3:
                frameLength = buffer.getUnsignedMedium(actualLengthFieldOffset);
                break;
            case 4:
                frameLength = buffer.getUnsignedInt(actualLengthFieldOffset);
                break;
    ……
    

    我们这里进入的是4,还记得在编码时候的开头的4位占位字节吗?跟踪进去发现。


    public int getInt(int index) {
            return  (array[index]     & 0xff) << 24 |
                    (array[index + 1] & 0xff) << 16 |
                    (array[index + 2] & 0xff) <<  8 |
                    (array[index + 3] & 0xff) <<  0;
        }
    

    原来,当初在编码时,在流开头增加的4字节的字符是做这个的。他记录了当前了这个对象流的长度,便于在解码时候准确的计算出该对象流的长度,正确解码。看来,我们如果我们自己写的对象编码解码的工具,要考虑的还有很多啊。

    附:LengthFieldBasedFrameDecoder的JavaDoc

     

    /**
    * A decoder that splits the received {@link ChannelBuffer}s dynamically by the
    * value of the length field in the message.  It is particularly useful when you
    * decode a binary message which has an integer header field that represents the
    * length of the message body or the whole message.
    * <p>
    * {@link LengthFieldBasedFrameDecoder} has many configuration parameters so
    * that it can decode any message with a length field, which is often seen in
    * proprietary client-server protocols. Here are some example that will give
    * you the basic idea on which option does what.
    *
    * <h3>2 bytes length field at offset 0, do not strip header</h3>
    *
    * The value of the length field in this example is <tt>12 (0x0C)</tt> which
    * represents the length of "HELLO, WORLD".  By default, the decoder assumes
    * that the length field represents the number of the bytes that follows the
    * length field.  Therefore, it can be decoded with the simplistic parameter
    * combination.
    * <pre>
    * <b>lengthFieldOffset</b>   = <b>0</b>
    * <b>lengthFieldLength</b>   = <b>2</b>
    * 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" |
    * +——–+—————-+      +——–+—————-+
    * </pre>
    *
    * <h3>2 bytes length field at offset 0, strip header</h3>
    *
    * Because we can get the length of the content by calling
    * {@link ChannelBuffer#readableBytes()}, you might want to strip the length
    * field by specifying <tt>initialBytesToStrip</tt>.  In this example, we
    * specified <tt>2</tt>, that is same with the length of the length field, to
    * strip the first two bytes.
    * <pre>
    * lengthFieldOffset   = 0
    * lengthFieldLength   = 2
    * lengthAdjustment    = 0
    * <b>initialBytesToStrip</b> = <b>2</b> (= the length of the Length field)
    *
    * BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
    * +——–+—————-+      +—————-+
    * | Length | Actual Content |—–>| Actual Content |
    * | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
    * +——–+—————-+      +—————-+
    * </pre>
    *
    * <h3>2 bytes length field at offset 0, do not strip header, the length field
    *     represents the length of the whole message</h3>
    *
    * In most cases, the length field represents the length of the message body
    * only, as shown in the previous examples.  However, in some protocols, the
    * length field represents the length of the whole message, including the
    * message header.  In such a case, we specify a non-zero
    * <tt>lengthAdjustment</tt>.  Because the length value in this example message
    * is always greater than the body length by <tt>2</tt>, we specify <tt>-2</tt>
    * as <tt>lengthAdjustment</tt> for compensation.
    * <pre>
    * lengthFieldOffset   =  0
    * lengthFieldLength   =  2
    * <b>lengthAdjustment</b>    = <b>-2</b> (= 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" |
    * +——–+—————-+      +——–+—————-+
    * </pre>
    *
    * <h3>3 bytes length field at the end of 5 bytes header, do not strip header</h3>
    *
    * The following message is a simple variation of the first example.  An extra
    * header value is prepended to the message.  <tt>lengthAdjustment</tt> is zero
    * again because the decoder always takes the length of the prepended data into
    * account during frame length calculation.
    * <pre>
    * <b>lengthFieldOffset</b>   = <b>2</b> (= the length of Header 1)
    * <b>lengthFieldLength</b>   = <b>3</b>
    * 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" |
    * +———-+———-+—————-+      +———-+———-+—————-+
    * </pre>
    *
    * <h3>3 bytes length field at the beginning of 5 bytes header, do not strip header</h3>
    *
    * This is an advanced example that shows the case where there is an extra
    * header between the length field and the message body.  You have to specify a
    * positive <tt>lengthAdjustment</tt> so that the decoder counts the extra
    * header into the frame length calculation.
    * <pre>
    * lengthFieldOffset   = 0
    * lengthFieldLength   = 3
    * <b>lengthAdjustment</b>    = <b>2</b> (= 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" |
    * +———-+———-+—————-+      +———-+———-+—————-+
    * </pre>
    *
    * <h3>2 bytes length field at offset 1 in the middle of 4 bytes header,
    *     strip the first header field and the length field</h3>
    *
    * This is a combination of all the examples above.  There are the prepended
    * header before the length field and the extra header after the length field.
    * The prepended header affects the <tt>lengthFieldOffset</tt> and the extra
    * header affects the <tt>lengthAdjustment</tt>.  We also specified a non-zero
    * <tt>initialBytesToStrip</tt> to strip the length field and the prepended
    * header from the frame.  If you don't want to strip the prepended header, you
    * could specify <tt>0</tt> for <tt>initialBytesToSkip</tt>.
    * <pre>
    * lengthFieldOffset   = 1 (= the length of HDR1)
    * lengthFieldLength   = 2
    * <b>lengthAdjustment</b>    = <b>1</b> (= the length of HDR2)
    * <b>initialBytesToStrip</b> = <b>3</b> (= 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" |
    * +——+——–+——+—————-+      +——+—————-+
    * </pre>
    *
    * <h3>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</h3>
    *
    * Let's give another twist to the previous example.  The only difference from
    * the previous example is that the length field represents the length of the
    * whole message instead of the message body, just like the third example.
    * We have to count the length of HDR1 and Length into <tt>lengthAdjustment</tt>.
    * Please note that we don't need to take the length of HDR2 into account
    * because the length field already includes the whole header length.
    * <pre>
    * lengthFieldOffset   =  1
    * lengthFieldLength   =  2
    * <b>lengthAdjustment</b>    = <b>-3</b> (= the length of HDR1 + LEN, negative)
    * <b>initialBytesToStrip</b> = <b> 3</b>
    *
    * BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
    * +——+——–+——+—————-+      +——+—————-+
    * | HDR1 | Length | HDR2 | Actual Content |—–>| HDR2 | Actual Content |
    * | 0xCA | 0×0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
    * +——+——–+——+—————-+      +——+—————-+
    * </pre>
    *
    * @see LengthFieldPrepender
    */
    
    


About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规