Netty:LengthFieldBasedFrameDecoder分析

说明

概要介绍

具体请参考Netty官网API中LengthFieldBasedFrameDecoder的说明:https://netty.io/4.1/api/index.html

io.netty.handler.codec.LengthFieldBasedFrameDecoder是一个ByteToMessageDecoder的实现类,它基于长度字段对收到的ByteBuf进行拆分。

在构建LengthFieldBasedFrameDecoder实例时,调用者可以输入下面参数,控制拆包的行为:

  • ByteOrder:长度字段的字节序,有BIG_ENDIAN和LITTLE_ENDIAN两种情况。默认值是BIG_ENDIAN。当长度字段大于1个字节的时候有用。
  • maxFrameLength:帧(frame)的最大字节数。
  • lengthFieldOffset:长度字段的偏移字节数。是相对于帧的开始。
  • lengthFieldLength:长度字段的字节数。只支持1个字节、2个字节、3个字节、4个字节、8个字节。LengthFieldBasedFrameDecoder的下面这个函数对长度字段的字节数做了校验:
protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
    buf = buf.order(order);
    long frameLength;
    switch (length) {
    case 1:
        frameLength = buf.getUnsignedByte(offset);
        break;
    case 2:
        frameLength = buf.getUnsignedShort(offset);
        break;
    case 3:
        frameLength = buf.getUnsignedMedium(offset);
        break;
    case 4:
        frameLength = buf.getUnsignedInt(offset);
        break;
    case 8:
        frameLength = buf.getLong(offset);
        break;
    default:
        throw new DecoderException(
                "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
    }
    return frameLength;
}
  • lengthAdjustment:默认值为0。增加到长度字段的值的补偿值。该补偿值=该帧中长度字段后面的字节数 - 长度字段的值。
  • initialBytesToStrip:默认值为0。解包后的帧开头多少字节应该被去掉。
  • failFast:默认值为true。当值为true的时候,解码器如果看到帧的长度大于maxFrameLength,就抛出TooLongFrameException异常,而不管帧后面的数据有没有实际读取。当值为false的时候,只有当解码器实际读取的字节数超过maxFrameLength时,才会抛出TooLongFrameException异常。

几种组合情况的示意性说明

开始是占2个字节的长度字段,长度字段的值表示实际内容的字节数,不去掉头部字段

lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0

  解码前 (14 字节)                 解码后 (14 字节)
 +--------+----------------+      +--------+----------------+
 |长度    | 实际内容         |----> |长度| 实际内容            |
 | 0x000C | "HELLO, NETTY" |      | 0x000C | "HELLO, NETTY" |
 +--------+----------------+      +--------+----------------+

开始是占2个字节的长度字段,长度字段的值表示实际内容的字节数,去掉头部字段

lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2(长度字段的值)

  解码前 (14 字节)                 解码后 (12 字节)
 +--------+----------------+      +----------------+
 |长度    | 实际内容         |----> |长度| 实际内容    |
 | 0x000C | "HELLO, NETTY" |      | "HELLO, NETTY" |
 +--------+----------------+      +----------------+

开始是占2个字节的长度字段,长度字段的值表示整个报文的字节数,不去掉头部字段

lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2(长度字段的字节数)
initialBytesToStrip = 0

  解码前 (14 字节)                 解码后 (14 字节)
 +--------+----------------+      +--------+----------------+
 |长度    | 实际内容         |----> |长度| 实际内容            |
 | 0x000E | "HELLO, NETTY" |      | 0x000E | "HELLO, NETTY" |
 +--------+----------------+      +--------+----------------+

开始是占2个字节头部字段,然后是占3个字节的长度字段,长度字段的值表示实际内容的字节数,不去掉头部字段

lengthFieldOffset = 2(头部字段占了2个字节)
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0

 解码前 (17 字节)                                解码后 (17 字节)
 +----------+----------+----------------+      +----------+----------+----------------+
 | 头部字段  |  长度     | 实际内容        |----->| 头部字段   |  长度    | 实际内容        |
 |  0x6801  | 0x00000C | "HELLO, NETTY" |      |  0x6801  | 0x00000C | "HELLO, NETTY" |
 +----------+----------+----------------+      +----------+----------+----------------+

开始是占3个字节的长度字段,然后是占2个字节的头部,长度字段的值表示实际内容的字节数,不去掉头部字段

lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2(头部字段占用2个字节)
initialBytesToStrip = 0

 解码前 (17 字节)                                解码后 (17 字节)
 +----------+----------+----------------+      +----------+----------+----------------+
 | 长度      |  头部字段 | 实际内容        |----->| 长度      |  头部字段 | 实际内容        |
 |  0x00000C | 0x6801  | "HELLO, NETTY" |      |  0x00000C | 0x6801  | "HELLO, NETTY" |
 +----------+----------+----------------+      +----------+----------+----------------+

开始是占1个字节的头部1,然后是占2个字节的长度字段,然后是占1个字节的头部2,长度字段的值表示实际内容的字节数,去掉头部1和长度字段

lengthFieldOffset = 1(头部1占1个字节)
lengthFieldLength = 2
lengthAdjustment = 1(头部2占1个字节)
initialBytesToStrip = 3(头部1+长度字段合起来占3个字节)

 解码前 (16 字节)                               解码后 (13 字节)
 +------+--------+------+----------------+      +------+----------------+
 | 头部1 | 长度   |头部2   | 实际内容       |----->|头部2  | 实际内容        |
 | 0x68 | 0x000C | 0x01 | "HELLO, WORLD" |      | 0x01 | "HELLO, WORLD" |
 +------+--------+------+----------------+      +------+----------------+

开始是占1个字节的头部1,然后是占2个字节的长度字段,然后是占1个字节的头部2,长度字段的值表示整个报文的字节数,去掉头部1和长度字段

lengthFieldOffset = 1(头部1占1个字节)
lengthFieldLength = 2
lengthAdjustment = -3(头部1 和 长度字段合起来占3个字节)
initialBytesToStrip = 3(头部1+长度字段合起来占3个字节)

 解码前 (16 字节)                               解码后 (13 字节)
 +------+--------+------+----------------+      +------+----------------+
 | 头部1| 长度    |头部2  | 实际内容        |----->|头部2  | 实际内容        |
 | 0x68 | 0x0010 | 0x01 | "HELLO, WORLD" |      | 0x01 | "HELLO, WORLD" |
 +------+--------+------+----------------+      +------+----------------+

代码示例

示例:用LengthFieldBasedFrameDecoder基于长度字段将整个帧解析下来,传递给后端的处理业务逻辑的ChannelHandler

本示例验证场景:
从客户端发送一个12个字节的业务报文给服务端。服务端用LengthFieldBasedFrameDecoder将整个报文收取下来,传递给后续的处理业务逻辑的ChannelHandler。

发送的报文各字节介绍:
第1个字节:头部字段1
第2个字节:头部字段2
第3个字节:长度字段,值为6,指明实际内容的长度
第4-9个字节:共6个字节,代表实际内容
第10-11字节:共2个字节,尾部字段1
第12个字节:尾部字段2

服务端代码片段

package com.thb.power.server;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 服务端的主函数
 * @author thb
 *
 */
public class MainStation {
	private static final Logger logger = LogManager.getLogger();
	
	static final int PORT = Integer.parseInt(System.getProperty("port", "22335"));

	public static void main(String[] args) throws Exception {
		logger.traceEntry();
		
		// 配置服务器
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b =  new ServerBootstrap();
			b.group(bossGroup, workerGroup)
			 .channel(NioServerSocketChannel.class)
			 .option(ChannelOption.SO_BACKLOG, 100)
			 .handler(new LoggingHandler(LogLevel.INFO))
			 .childHandler(new MainStationInitializer());
			
			// 启动服务端
			ChannelFuture f = b.bind(PORT).sync();
			
			// 等待直到server socket关闭
			f.channel().closeFuture().sync();
		} finally {
			// 关闭所有event loops以便终止所有的线程
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
		
		logger.traceExit();
	}

}
package com.thb.power.server;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.thb.power.server.register.ServerRegisterRequestHandler;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class MainStationInitializer extends ChannelInitializer<SocketChannel> {
	private static final Logger logger = LogManager.getLogger();

	 @Override
	 public void initChannel(SocketChannel ch) throws Exception {
		 logger.traceEntry();
		 
		 ChannelPipeline p = ch.pipeline();		
		 
		 p.addLast(new LoggingHandler(LogLevel.INFO));
		 p.addLast(new LengthFieldBasedFrameDecoder(12, 2, 1, 3, 0));			
		 p.addLast(new ServerRegisterRequestHandler());
		 
		 logger.traceExit();
	 }
}
package com.thb.power.server.register;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ServerRegisterRequestHandler extends ChannelInboundHandlerAdapter {
	
	private static final Logger logger = LogManager.getLogger();
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		logger.traceEntry();
		
		ByteBuf m = (ByteBuf)msg;
		
		logger.info("readableBytes: " + m.readableBytes());
		logger.traceExit();
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

运行服务端

在这里插入图片描述

运行客户端,并向服务端发送总长度为12个字节的报文

在这里插入图片描述

查看服务端的输出

在这里插入图片描述
从输出可以看出,LengthFieldBasedFrameDecoder后面的ServerRegisterRequestHandler收到了12个字节的数据,解析成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值