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

全文详见个人独立博客:Java NIO框架Netty教程(十)-Object对象的连续收发解析分析

Java NIO框架Netty教程(十)-Object对象的连续收发解析分析如果您一直关注OneCoder,我们之前有两篇文章介绍关于Netty消息连续收发的问题。( 《Java NIO框架Netty教程(五)- 消息收发次数不匹配的问题 》、《 Java NIO框架Netty教程(七)-再谈收发信息次数问题 》)。如果您经常的”怀疑”和思考,我们刚介绍过了Object的传递,您是否好奇,在Object传递中是否会有这样的问题?如果Object流的字节截断错乱,那肯定是会出错的。Netty一定不会这么傻的,那么Netty是怎么做的呢? 我们先通过代码验证一下是否有这样的问题。(有问题的可能性几乎没有。) /** * 当绑定到服务端的时候触发,给服务端发消息。 * * @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(&quot;Hello action.&quot;); Command commandOne = new Command(); commandOne.setActionName(&quot;Hello action. One&quot;); Command command2 = new Command(); command2.setActionName(&quot;Hello action. Two&quot;); 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 | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" | * +------+--------+------+----------------+      +------+----------------+ * </pre> * * @see LengthFieldPrepender */icon-default.png?t=N7T8https://www.coderli.com/netty-object-continuous-readwrite/交流探讨,加入群聊【Java学习交流(982860385)】

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

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

/**
	 * 当绑定到服务端的时候触发,给服务端发消息。
	 * 
	 * @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(&quot;Hello action.&quot;);
		Command commandOne = new Command();
		commandOne.setActionName(&quot;Hello action. One&quot;);
		Command command2 = new Command();
		command2.setActionName(&quot;Hello action. Two&quot;);
		channel.write(command2);
		channel.write(command);
		channel.write(commandOne);
	}

打印结果:

Hello action. Two Hello action. Hello action. One

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

 全文详见个人独立博客:Java NIO框架Netty教程(十)-Object对象的连续收发解析分析

交流探讨,加入群聊【Java学习交流(982860385)】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值