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.
*/