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

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

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

 

01. /**
02. * 当绑定到服务端的时候触发,给服务端发消息。
03. *
04. * @author lihzh
05. * @alia OneCoder
06. */www.it165.net
07. @Override
08. public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
09. // 向服务端发送Object信息
10. sendObject(e.getChannel());
11. }
12.  
13. /**
14. * 发送Object
15. *
16. * @param channel
17. * @author lihzh
18. * @alia OneCoder
19. */
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. @Override
02. 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 the
003. * value of the length field in the message.  It is particularly useful when you
004. * decode a binary message which has an integer header field that represents the
005. * length of the message body or the whole message.
006. * <p>
007. * {@link LengthFieldBasedFrameDecoder} has many configuration parameters so
008. * that it can decode any message with a length field, which is often seen in
009. * proprietary client-server protocols. Here are some example that will give
010. * 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> which
015. * represents the length of "HELLO, WORLD".  By default, the decoder assumes
016. * that the length field represents the number of the bytes that follows the
017. * length field.  Therefore, it can be decoded with the simplistic parameter
018. * combination.
019. * <pre>
020. * <b>lengthFieldOffset</b>   = <b>0</b>
021. * <b>lengthFieldLength</b>   = <b>2</b>
022. * lengthAdjustment    = 0
023. * 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 calling
035. * {@link ChannelBuffer#readableBytes()}, you might want to strip the length
036. * field by specifying <tt>initialBytesToStrip</tt>.  In this example, we
037. * specified <tt>2</tt>, that is same with the length of the length field, to
038. * strip the first two bytes.
039. * <pre>
040. * lengthFieldOffset   = 0
041. * lengthFieldLength   = 2
042. * lengthAdjustment    = 0
043. * <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 field
053. *     represents the length of the whole message</h3>
054. *
055. * In most cases, the length field represents the length of the message body
056. * only, as shown in the previous examples.  However, in some protocols, the
057. * length field represents the length of the whole message, including the
058. * message header.  In such a case, we specify a non-zero
059. * <tt>lengthAdjustment</tt>.  Because the length value in this example message
060. * 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   =  0
064. * lengthFieldLength   =  2
065. * <b>lengthAdjustment</b>    = <b>-2</b> (= the length of the Length field)
066. * initialBytesToStrip =  0
067. *
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 extra
078. * header value is prepended to the message.  <tt>lengthAdjustment</tt> is zero
079. * again because the decoder always takes the length of the prepended data into
080. * 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    = 0
085. * initialBytesToStrip = 0
086. *
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 extra
097. * header between the length field and the message body.  You have to specify a
098. * positive <tt>lengthAdjustment</tt> so that the decoder counts the extra
099. * header into the frame length calculation.
100. * <pre>
101. * lengthFieldOffset   = 0
102. * lengthFieldLength   = 3
103. * <b>lengthAdjustment</b>    = <b>2</b> (= the length of Header 1)
104. * initialBytesToStrip = 0
105. *
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 prepended
117. * header before the length field and the extra header after the length field.
118. * The prepended header affects the <tt>lengthFieldOffset</tt> and the extra
119. * header affects the <tt>lengthAdjustment</tt>.  We also specified a non-zero
120. * <tt>initialBytesToStrip</tt> to strip the length field and the prepended
121. * header from the frame.  If you don't want to strip the prepended header, you
122. * could specify <tt>0</tt> for <tt>initialBytesToSkip</tt>.
123. * <pre>
124. * lengthFieldOffset   = 1 (= the length of HDR1)
125. * lengthFieldLength   = 2
126. * <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 field
138. *     represents the length of the whole message</h3>
139. *
140. * Let's give another twist to the previous example.  The only difference from
141. * the previous example is that the length field represents the length of the
142. * 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 account
145. * because the length field already includes the whole header length.
146. * <pre>
147. * lengthFieldOffset   =  1
148. * lengthFieldLength   =  2
149. * <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 LengthFieldPrepender
160. */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值