如果您一直关注OneCoder,我们之前有两篇文章介绍关于Netty消息连续收发的问题。( 《Java NIO框架Netty教程(五)- 消息收发次数不匹配的问题 》、《 Java NIO框架Netty教程(七)-再谈收发信息次数问题 》)。如果您经常的“怀疑”和思考,我们刚介绍过了Object的传递,您是否好奇,在Object传递中是否会有这样的问题?如果Object流的字节截断错乱,那肯定是会出错的。Netty一定不会这么傻的,那么Netty是怎么做的呢?
我们先通过代码验证一下是否有这样的问题。(有问题的可能性几乎没有。)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* 当绑定到服务端的时候触发,给服务端发消息。
*
* @author lihzh
* @alia OneCoder
*/
@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. TwoHello action.Hello action. One
一切正常。那么Netty是怎么分割对象流的呢?看看ObjectDecoder怎么做的。
在ObjectDecoder的基类LengthFieldBasedFrameDecoder中注释中有详细的说明。我们这里主要介绍一下关键的代码逻辑:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
@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位占位字节吗?跟踪进去发现。
1
2
3
4
5
6
|
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
*/
如非特别注明,本站内容均为OneCoder原创,转载请务必注明作者和原始出处。
本文地址:http://www.coderli.com/netty-object-continuous-readwrite