在第四章中为止我们基本了解了netty的基本原理,但是到我们实际的业务场景之间还有一部分内容需要我们深入讨论一下,就是netty解码和编码器。在本讲中我们以ByteToMessageDecoder为切入点,展开一系列解码器的讨论。
为呼应上下文我们有必要说一下,当客户端有消息发送过来后,我们eventLoop监听到事件后会触发读事件,进而调用pipeline 链中的 channelRead()回调函数,这也是我们本讲的入口函数。接下来我们就从ByteToMessageDecoder类的channelRead()回调函数开始展开讨论。
目录
抽象解码器ByteToMessageDecoder
客户端读取到的数据封装为为了ByteBuf类型的数据,所以进入第一个if,判断是否是第一次读取,判断的条件是cumulation Bytebuf,如果是第一次的话,肯定为null,不是的话就说明不是第一次读取。如果是第一次的话就是简单的引用变换,将我们的累加器bytebuf指向接受到的bytebuf类型的数据 data,如果不是第一次,也很简单,就是单纯的将我们读取到的数据拷贝到cumulation ByteBuf中,这里需要睁大眼睛看清楚cumulator 和 cumulation ,老弟就是在这块看花眼了,倒是怀疑人生。接下来就是调用具体的解码逻辑,我们点击进去看callDecode(ctx, cumulation, out),如果ByteBuf 中有可读的数据,第一个if的目的为如果有没有处理的解码过的数据,我们进行read消息传递,供下游消费。 decode(ctx, in, out)方法,这里调用具体的解码逻辑,我们点击进去看,其为抽象类供子类实现。这个out就是子类特定的解码格式接出来的对象,并放到我们的队列中去,这也是我们本章围绕分析的终点,我们将会在不同的子类看到不同的实现。在解析完成后,退出循环,回到原来的起点,看finnally 代码块,这里如果我们解析出对象,则向下传递read事件并将我们解码的对象传递进去。到此为止我们的整体流程都已经分析完成,接下来的章节的介绍都是围绕我们本类中尚未实现的一个抽象方法decode(ctx, in, out)方法进行不同功能的实现。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//这是我们客户端读取到的数据封账为了ByteBuf
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
//判断是否是第一次读取,判断的条件是cumulation Bytebuf,如果是第一次的话,肯定为null,不是的话就说明不是第一次读取。
first = cumulation == null;
if (first) {
//如果是第一次的话就是简单的引用变换
cumulation = data;
} else {
//如果不是第一次,也很简单,就是单纯的将我们读取到的数据拷贝到cumulation ByteBuf中,这里需要睁大眼睛看清楚cumulator 和 cumulation ,老弟就是在这块看花眼了,倒是怀疑人生
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
//这里调用具体的解码逻辑,我们点击进去看
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++ numReads >= discardAfterReads) {
// We did enough reads already try to discard some bytes so we not risk to see a OOME.
// See https://github.com/netty/netty/issues/4275
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
//如果我们解析出对象,则向下传递
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
//我们看到其只是简单的拷贝,一个if else逻辑,就是如果容量存放不下就扩容
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
ByteBuf buffer;
if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
|| cumulation.refCnt() > 1) {
// Expand cumulation (by replace it) when either there is not more room in the buffer
// or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
// duplicate().retain().
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
} else {
buffer = cumulation;
}
buffer.writeBytes(in);
in.release();
return buffer;
}
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
//如果ByteBuf 中有可读的数据
while (in.isReadable()) {
int outSize = out.size();
//如果有没有处理的解码过的数据,我们进行read消息传递,供下游消费
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// Check if this handler was removed before continuing with decoding.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See:
// - https://github.com/netty/netty/issues/4635
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
//保存客户端可读数据长度
int oldInputLength = in.readableBytes();
//这里调用具体的解码逻辑,我们点击进去看,其为抽象类供子类实现。这个out就是子类特定的解码格式接出来的对象,并放到我们的队列中去,这也是我们本章围绕分析的终点,我们将会在不同的子类看到不同的实现
decode(ctx, in, out);
// Check if this handler was removed before continuing the loop.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See https://github.com/netty/netty/issues/1664
if (ctx.isRemoved()) {
break;
}
//如果没有解析到对象
if (outSize == out.size()) {
//并且我们也没有读取数据,那么退出循环
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);
}
}
//我们看到该刚发为抽象方法供子类实现
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
基于固定长度解码器分析
该解码器比较简单,我们已经添加了注释,小伙伴 们参见注释即可。
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
private final int frameLength;
//此处为我们构造时传递进去的固定长度的长度大小
public FixedLengthFrameDecoder(int frameLength) {
if (frameLength <= 0) {
throw new IllegalArgumentException(
"frameLength must be a positive integer: " + frameLength);
}
this.frameLength = frameLength;
}
//实现父类ByteToMessageDecoder 的模板方法
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//并调用具体的自己的实现方法,将解析到的对象填充到out队列中去
Object decoded = decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
//如果接受到的数据小于固定长度,就返回为空,如果大于固定长度就读取固定长度的字节数据byteBuf对象返回
protected Object decode(
@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (in.readableBytes() < frameLength) {
return null;
} else {
return in.readRetainedSlice(frameLength);
}
}
}
行解码器分析
LineBasedFrameDecoder,行解码器,首先我们对其成员变量进行讲解,参见代码块1,接线来我们看具体的解码的代码实现,逻辑和之前相同,同样把具体的实现封装到了 decode(ctx, in);方法中,我们点击去,我们基本可以分为以下几点:
丢弃模式
1)如果读取到分割符,直接将指针知道分割符后,并丢弃前面的,然后恢复模式
2)如果没有读取到分割符,直接将指针设置到写位置,丢弃前面的
非丢弃模式
1)如果读到分割符,超过长度,将指针指向分割符之后,然后丢弃
2)如果读到分割符,没有超过长度,直接返回
3)如果没有读取到分割符,超过长度,丢弃,并设置为丢弃模式
//每行允许的最大字节数据,如果超过,则进行本行的丢弃
private final int maxLength;
//是否快速失败,如果是的话,立即抛出异常
private final boolean failFast;
//是否跳过行分隔符
private final boolean stripDelimiter;
//是否是丢弃模式
private boolean discarding;
//丢弃模式下,丢弃的字节数据
private int discardedBytes;
//逻辑和之前相同
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 {
//首先遍历bytebuf找到我们需要的行分隔符所指向的索引
final int eol = findEndOfLine(buffer);
//不是丢弃模式
if (!discarding) {
//如果读取到完整的一行
if (eol >= 0) {
final ByteBuf frame;
//计算得出该行的长度
final int length = eol - buffer.readerIndex();
//计算分割符的长度 \n 的话为1 ,\r\n的话为2
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
//如果长度大于最大限制长度
if (length > maxLength) {
//直接将bytebuf的读指针移动到分割符后面
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;
}
}
基于分隔符解码器分析
DelimiterBasedFrameDecoder,该逻辑基本与上面相同,我们简单的粘贴处代码逻辑
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
if (lineBasedDecoder != null) {
return lineBasedDecoder.decode(ctx, buffer);
}
// Try all delimiters and choose the delimiter which yields the shortest frame.
int minFrameLength = Integer.MAX_VALUE;
ByteBuf minDelim = null;
for (ByteBuf delim: delimiters) {
int frameLength = indexOf(buffer, delim);
if (frameLength >= 0 && frameLength < minFrameLength) {
minFrameLength = frameLength;
minDelim = delim;
}
}
if (minDelim != null) {
int minDelimLength = minDelim.capacity();
ByteBuf frame;
if (discardingTooLongFrame) {
// We've just finished discarding a very large frame.
// Go back to the initial state.
discardingTooLongFrame = false;
buffer.skipBytes(minFrameLength + minDelimLength);
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast) {
fail(tooLongFrameLength);
}
return null;
}
if (minFrameLength > maxFrameLength) {
// Discard read frame.
buffer.skipBytes(minFrameLength + minDelimLength);
fail(minFrameLength);
return null;
}
if (stripDelimiter) {
frame = buffer.readRetainedSlice(minFrameLength);
buffer.skipBytes(minDelimLength);
} else {
frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
}
return frame;
} else {
if (!discardingTooLongFrame) {
if (buffer.readableBytes() > maxFrameLength) {
// Discard the content of the buffer until a delimiter is found.
tooLongFrameLength = buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
discardingTooLongFrame = true;
if (failFast) {
fail(tooLongFrameLength);
}
}
} else {
// Still discarding the buffer since a delimiter is not found.
tooLongFrameLength += buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
}
基于长度域解码器分析
LengthFieldBasedFrameDecoder,长度域解码器,我们先看一下他实现功能参见注解,我们列出代码,供读者自己分析
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;
}