OneCoder Avatar
OneCodercoderli.com · 937 篇博文
{ } Java

Java 服务端架构

Spring、Netty、日志框架与工程化实战

🎨 视觉封面

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

📅 2012-07-27·✍️ onecoder·计算中...·⏱️ 32 分钟
#Netty

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

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

JAVA
/**
	 * 当绑定到服务端的时候触发,给服务端发消息。
	 * 
	 * @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. Two Hello action. Hello action. One

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

JAVA
@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位占位字节吗?跟踪进去发现。

JAVA
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字节的字符是做这个的。他记录了当前了这个对象流的长度,便于在解码时候准确的计算出该对象流的长度,正确解码。看来,我们如果我们自己写的对象编码解码的工具,要考虑的还有很多啊。

附:LengthFieldBasedFrameDecoderJavaDoc

/**
* 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.
* * {@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.
*
* *
* The value of the length field in this example is * 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.
* * * * 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" |
* +--------+----------------+      +--------+----------------+
*
*
* *
* Because we can get the length of the content by calling
* {@link ChannelBuffer#readableBytes()}, you might want to strip the length
* field by specifying * specified * strip the first two bytes.
* * lengthFieldOffset   = 0
* lengthFieldLength   = 2
* lengthAdjustment    = 0
* *
* BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
* +--------+----------------+      +----------------+
* | Length | Actual Content |----->| Actual Content |
* | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
* +--------+----------------+      +----------------+
*
*
* *     represents the length of the whole message *
* 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
* * is always greater than the body length by * as * * lengthFieldOffset   =  0
* lengthFieldLength   =  2
* * initialBytesToStrip =  0
*
* BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
* +--------+----------------+      +--------+----------------+
* | Length | Actual Content |----->| Length | Actual Content |
* | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
* +--------+----------------+      +--------+----------------+
*
*
* *
* The following message is a simple variation of the first example.  An extra
* header value is prepended to the message.  * again because the decoder always takes the length of the prepended data into
* account during frame length calculation.
* * * * 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" |
* +----------+----------+----------------+      +----------+----------+----------------+
*
*
* *
* 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 * header into the frame length calculation.
* * lengthFieldOffset   = 0
* lengthFieldLength   = 3
* * 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" |
* +----------+----------+----------------+      +----------+----------+----------------+
*
*
* *     strip the first header field and the length field *
* 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 * header affects the * * header from the frame.  If you don't want to strip the prepended header, you
* could specify * * lengthFieldOffset   = 1 (= the length of HDR1)
* lengthFieldLength   = 2
* * *
* BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
* +------+--------+------+----------------+      +------+----------------+
* | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
* | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
* +------+--------+------+----------------+      +------+----------------+
*
*
* *     strip the first header field and the length field, the length field
*     represents the length of the whole message *
* 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 * 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.
* * lengthFieldOffset   =  1
* lengthFieldLength   =  2
* * *
* BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
* +------+--------+------+----------------+      +------+----------------+
* | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
* | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
* +------+--------+------+----------------+      +------+----------------+
*
*
* @see LengthFieldPrepender
*/

💡 OneCoder 资源指引

所有代码开源上传至 GitHub:yummy-code 仓库 · GESP 专题站:GESP WIKI

🤝 技术交流与答疑

欢迎加入:C++ GESP/CSP 考级答疑群(688906745)Java/Python交流群(982860385),点击可直接加群。

📚

猜你想读 · 相关文章推荐

Java 架构演进与实战 · Netty⏱️ 2 分钟

开源Java高性能NIO框架推荐 - Netty

作为一个Java程序员,多方面的知识储备是相当重要的。面对各种的情况,都有自己的解决方案也是一种能力的体现。Java优秀的开源项目很多,除了传统的SSH,作为一个Java程序员,你应该知道的更多:) Netty简介 Netty 是一个异步的,事件驱动的网络编程框架和工具,使用Netty可以快速开发出可维护的,高性能、高...

阅读全文 →
Java 架构演进与实战 · Netty⏱️ 14 分钟

Netty Object传输问题解决

使用Netty开发分布式框架,对象的传输是基本的需求。但是,在开发中却总遇到服务端接受不到客户端的对象发送数据。遂动手解决。 如果看到最后,你可能会发现,笔者的问题是那么的二,不适合你。。所以,你可以决定不看,或者只看样例代码。笔者记录的是,个人的解决过程。 先看一下官方提供的,对象传输的样例代码:(核心部分) 代码很...

阅读全文 →
Java 架构演进与实战 · Netty⏱️ 7 分钟

Java NIO框架Netty教程(一) - Hello Netty

先啰嗦两句,如果你还不知道Netty是做什么的能做什么。那可以先简单的搜索了解一下。我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序。具体能做什么,各位可以尽量发挥想象。技术,是服务于人而不是局限住人的。 Netty的简介和下载可参考:开源Java高性能NIO框架推荐。注意,此时的最新版已经为3....

阅读全文 →
OneCoder

OneCoder (lihongzheshuai)

一个中年人的自留地,记录学习 C++、GESP/NOI、Java、Python 与算法架构的心得体会。本站唯一网址:coderli.com

💬 读者留言与交流

0 条讨论
✨ 支持 Markdown 语法格式
还没有留言,快来成为第一个讨论者吧!