一.解码概述
数据在网络中传输是二进制字节流,数据到达服务端后,需要将二进制字节流转换为java对象,或者对数据进行切分,防止拆包/粘包现象,而处理这些功能点就需要netty的解码器
如果使用netty的解码器? netty解码器也是一个channelHandler,只需要加入channel中即可,举例如下:
pipeline.addLast("encode", new FixedLengthFrameDecoder(6));
二.抽象解码器ByteToMessageDecoder
解码器的抽象父类是ByteToMessageDecoder,当消息通过网络到达channel时候,会调用channelRead()方法,通过pipline调用到解码器,代码如下
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
//cumulation 累加器
first = cumulation == null;
if (first) {
// 数据第一次进来,累加器等于数据
cumulation = data;
} else {
// 非第一次进来,累加器会把当前数据添加到累加器中
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
// 调用decode方法,进行解码,把解码后的数据放在out中
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
// 累加器的数据读取后,判断累加器是否还有数据
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
//没有数据,就回收累加器,重新赋值累加器为null
cumulation.release();
cumulation = null;
} else if (++ numReads >= discardAfterReads) {
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
紧着进入
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
//1. 如果累加器中有数据,就一直循环
while (in.isReadable()) {
int outSize = out.size();
if (outSize > 0) {
//2.如果outsize里面有解析后的对象,就直接往后面触发channelRead()方法
fireChannelRead(ctx, out, outSize);
out.clear();
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
int oldInputLength = in.readableBytes();
//进入子类方法,完成解析从左
decode(ctx, in, out);
if (ctx.isRemoved()) {
break;
}
// 如果outsize数量没有变化
if (outSize == out.size()) {
// byteBuf里面数据和旧的数据一样,说明这一次没有读取,直接跳出循环
if (oldInputLength == in.readableBytes()) {
break;
} else {
continue;
}
}
if (oldInputLength == in.readableBytes()) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) +
".decode() did not read anything but decoded a message.");
}
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Throwable cause) {
throw new DecoderException(cause);
}
}
核心方法 decode(ctx, in, out);
- 使用模板设计模式,父类中定义,到具体的子类去实现
三.几种常见的解码器分析
由上面可知,解码器的抽象父类中核心方法decode(ctx, in, out),需要不同的子类去实现,下面我们来看看常用的几种解码器
1.固定长度解码器
// 构造方法数字代表,长度大小
pipeline.addLast("encode", new FixedLengthFrameDecoder(6));
看他的decode方法
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
继续进入
protected Object decode( ChannelHandlerContext ctx, ByteBuf in) throws Exception {
//判断byteBuf可读长度是否小于"数据报的长度"
if (in.readableBytes() < frameLength) {
return null;
} else {
// 不小于,直接读取frameLength的数据
return in.readRetainedSlice(frameLength);
}
}
2.行分隔符解码器
pipelie.addLast("encode", new LineBasedFrameDecoder(8));
看他的decoce方法
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
继续进入
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//1.找到分隔符的索引(判断分隔符是\r\n还是\n)
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
//如果数据报长度大于最大长度,触发fail方法
if (length > maxLength) {
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
// 截取到第一个换行符前的数据,并跳过换行符的长度
if (stripDelimiter) {
frame = buffer.readRetainedSlice(length);
buffer.skipBytes(delimLength);
} else {
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
} else {
final int length = buffer.readableBytes();
if (length > maxLength) {
discardedBytes = length;
buffer.readerIndex(buffer.writerIndex());
discarding = true;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
} else {
if (eol >= 0) {
final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
if (!failFast) {
fail(ctx, length);
}
} else {
discardedBytes += buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());
}
return null;
}
}
上面代码,主要逻辑是
- 找到第一个换行符的索引
- 判断长度是否大于最大长度,如果大于,就传播异常事件
- 截取第一个换行符前的数据,并跳过换行符的长度
- 把截取的带有回车符的数据返回,存入outlist中,往下传递
3.动态根据长度字段值的解码器
pipeline.addLast("encode", new LengthFieldBasedFrameDecoder(100,1,2,0,0));
这个类上面举例很多例子,主要就是有一个字段记录该数据的长度,所以需要传递
- 1.lengthFieldOffset:长度字段的偏移量
- 2.lengthFieldLength:长度字段自己占据的长度
- 3.lengthAdjustment:从该长度字段到真正数据之间的调整量
- 4.initialBytesToStrip:截取数据时候,从哪里开始 这个类的decode方法
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (discardingTooLongFrame) {
long bytesToDiscard = this.bytesToDiscard;
int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
in.skipBytes(localBytesToDiscard);
bytesToDiscard -= localBytesToDiscard;
this.bytesToDiscard = bytesToDiscard;
failIfNecessary(false);
}
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
if (frameLength < 0) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"negative pre-adjustment length field: " + frameLength);
}
frameLength += lengthAdjustment + lengthFieldEndOffset;
if (frameLength < lengthFieldEndOffset) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than lengthFieldEndOffset: " + lengthFieldEndOffset);
}
if (frameLength > maxFrameLength) {
long discard = frameLength - in.readableBytes();
tooLongFrameLength = frameLength;
if (discard < 0) {
// buffer contains more bytes then the frameLength so we can discard all now
in.skipBytes((int) frameLength);
} else {
// Enter the discard mode and discard everything received so far.
discardingTooLongFrame = true;
bytesToDiscard = discard;
in.skipBytes(in.readableBytes());
}
failIfNecessary(true);
return null;
}
// never overflows because it's less than maxFrameLength
int frameLengthInt = (int) frameLength;
if (in.readableBytes() < frameLengthInt) {
return null;
}
if (initialBytesToStrip > frameLengthInt) {
in.skipBytes(frameLengthInt);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than initialBytesToStrip: " + initialBytesToStrip);
}
in.skipBytes(initialBytesToStrip);
// extract frame
int readerIndex = in.readerIndex();
int actualFrameLength = frameLengthInt - initialBytesToStrip;
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
in.readerIndex(readerIndex + actualFrameLength);
return frame;
}
这里比较复杂了,核心就是通过前面设置的四个值,去bytebuf中取值,兄弟们,可以自行debug一下,就能很清晰了
四.总结
解码器作为一个channelHandler添加到pipline中,当数据到达服务端时候,会触发channel中pipline的inbount类型handler的channelRead方法,在解码器中,会
- 1.累加字节流
- 2.调用子类decode方法,更具不同的解码规则进行解析
- 3.把解析后的对象放在out中,向下传递
五.累加器的作用
累加器是为了处理netty的拆包和粘包事件,当发生拆包/粘包事件的时候,一个butyBuf中可以包含半个/一个/多个报文的数据,这个时候,就把读取到的数据都追加到累加器中,在decod方法中,会进行判断,如果当前数据不够组成一个完整的数据包,说明数据没有全部到来,前一部分数据不会被处理,继续存在累加器中,等后一部分数据到达后,继续存入累加器中,再次对数据进行处理,从而得到完整的数据包