ByteToMessageDecoder解码器
业务逻辑如下:
- 自定义Decoder拿到tcp的数据,按照支持的协议标记去匹配确定该数据采用的协议。
- 将协议标记绑定到channel的attr中。
- 向当前的ChannelPipeline中添加协议指定的Decoder。
代码大概逻辑如下:
NettyServer:
.......
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("protocolDecoder", new ProtocolDecoder());
pipeline.addLast("businessHandler", new BusinessHandler());
.......
ProtocolDecoder:
public class ProtocolDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
int len = byteBuf.readableBytes();
// 包头做标记位,后面可以重新回到数据包头开始读数据
byteBuf.markReaderIndex();
// 有数据时开始读数据包
byte[] src = new byte[len];
//复制数据至数组
byteBuf.getBytes(byteBuf.readerIndex(), src);
//数据处理判断协议的逻辑代码, 省略
.........
//根据协议添加特定的粘包拆包处理Decoder
pipeline.addAfter("protocolDecoder","lengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(1024, 1, 1));
//由于在此向out数组中添加的非ByteBuf对象,导致LengthFieldBasedFrameDecoder的decode不生效
//out.add(src);//错误
out.add(byteBuf);//正确
}
}
原因:
//经过debug发现触发decode方法的大概流程如下
/*
*首先经过AbstractChannelHandlerContext的invokeChannelRead方法
*->触发具体的ByteToMessageDecoder的实现对象的channelRead方法
*/
//chanelRead方法部分源码如下:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {//1
........
try {
..........
fireChannelRead(ctx, out, size);//2
} finally {
out.recycle();
}
}else{
ctx.fireChannelRead(msg);//3
}
}
导致编写的动态添加的粘包拆包Decoder生效的原因是:由于向out数组中添加的非ByteBuf对象导致上述方法时没有调用#2的代码。因此在pipeline包含多个ByteToMessageDecoder时应该保证非最后一个的out数组中添加的均为ByteBuf对象。
经过测试,上述解决方案能够解决多个Decoder的问题,但是不知道为什么handler处理完之后会报异常;技术太菜debug了很久也没找出来问题在哪,没办法只能改思路了 (有知道的大佬希望能点拨一下,非常感谢 !)
修改方案如下,不用两个Decoder了而是将LengthFieldBasedFrameDecoder源码修改成自己想要的模式:
创建新的Decoder复制LengthFieldBasedFrameDecoder源码然后修改decode方法
public class UnpackingDecoder extends ByteToMessageDecoder {
//LengthFieldBasedFrameDecoder源码内容
//.......................
//源码方法
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//以下是添加部分
int len = in.readableBytes();
// 包头做标记位,后面可以重新回到数据包头开始读数据
in.markReaderIndex();
// 有数据时开始读数据包
byte[] src = new byte[len];
in.getBytes(in.readerIndex(), src);
//根据src数据按照指定的协议规则判断该channel属于哪个协议,然后将该协议标志绑定至channel
//绑定完成后动态给当前解码器对象设置属性值
initProperties(当前协议指定的包解析属性);
//源码部分
Object decoded = this.decode(ctx, in);
if (decoded != null) {
//这里为添加逻辑,将bytebuf转换成tcprequest对象进行业务处理
TcpRequest tcpRequest = new TcpRequest(src, protocol);
out.add(tcpRequest);
}
}
//添加方法
private void initProperties(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
this.byteOrder = (ByteOrder) ObjectUtil.checkNotNull(byteOrder, "byteOrder");
ObjectUtil.checkPositive(maxFrameLength, "maxFrameLength");
ObjectUtil.checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");
ObjectUtil.checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");
if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {
throw new IllegalArgumentException("maxFrameLength (" + maxFrameLength + ") must be equal to or greater than lengthFieldOffset (" + lengthFieldOffset + ") + lengthFieldLength (" + lengthFieldLength + ").");
} else {
this.maxFrameLength = maxFrameLength;
this.lengthFieldOffset = lengthFieldOffset;
this.lengthFieldLength = lengthFieldLength;
this.lengthAdjustment = lengthAdjustment;
this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
this.initialBytesToStrip = initialBytesToStrip;
this.failFast = failFast;
}
}
}