解码器
对于使用netty框架的我们来说,最重要的就是业务逻辑,在netty中就体现在在pipeline中添加对应的处理器。
在处理器中我们可以完成我们需要完成的业务,这种处理器大致可以分为三种,入站、出战和出入站处理器。
我们可以通过实现入站或者出站的处理器接口来自定义处理器,同时netty也提供了大量处理器。
这里我们讨论的是解码器,同时也属于入站处理器,这种处理器可以帮助我们解决粘包拆包的问题,同时也几乎是通信必不可少的处理器。
在此之前我们得先认识以下所有解码器的抽象类ByteToMessageDecoder
,可以说解码器就是通过实现这个抽象类来实现解码的功能的。
ByteToMessageDecoder
在这个类中已经实现了channelRead
方法,所以实现的解码器都会进入到这个方法接收数据,这里就不细致地解析。
在这个方法中主要做的事就是将数据接收并放入缓存中,如果不是第一次拿到数据就把这次拿到的数据和之前的数据合并,将数据交给callDecode
方法判断。
对于数据的判断结果有两个,一个是满足解码条件,另一个是不满足解码条件。
在满足解码条件情况下,会将解码后的数据放在ByteBuf中,这个ByteBuf会放在一个list中,随后这个list便会通过fireChannelRead
方法传到下一个处理器。
在不满足解码条件的情况下,也会将数据传到下一个处理器中,但是传输的数据list是空的。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
if (first) {
//第一次直接赋值
cumulation = data;
} else {
//累加
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} 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);
}
}
接下来是decodeRemovalReentryProtection
方法
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
while (in.isReadable()) {
int outSize = out.size();
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();
decodeRemovalReentryProtection(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 (Exception cause) {
throw new DecoderException(cause);
}
}
在这个方法中真正调用了解码的方法
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
decodeState = STATE_CALLING_CHILD_DECODE;
try {
//调用子类的解码方法
decode(ctx, in, out);
} finally {
boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
decodeState = STATE_INIT;
if (removePending) {
handlerRemoved(ctx);
}
}
}
不过这个解码的方法是一个抽象方法,需要子类实现,之所以有那么多解码器就是因为实现了各自的decode
方法
接下来会介绍几个特殊的解码器如下
- FixedLengthFrameDecoder
- LineBasedFrameDecoder
- DelimiterBasedFrameDecoder
- LengthFieldBasedFrameDecoder
FixedLengthFrameDecoder
FixedLengthFrameDecoder是长度解码器,通过传输过来的长度来切分数据,只有达到指定的长度才会将数据传下去。
注意每次传输的数据长度都是指定的长度
FixedLengthFrameDecoder fixedLengthFrameDecoder = new FixedLengthFrameDecoder(10);
具体的解码方法,判断是否到达指定的长度,如果到达就会读取指定长度的数据(ByteBuf的读指定会移动)。
如果没有到达就会返回null
@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(
@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (in.readableBytes() < frameLength) {
return null;
} else {
return in.readRetainedSlice(frameLength);
}
}
LineBasedFrameDecoder
基于换行的解码器,遇到\n
或者\n\t
就会开始解码,将数据传递下去,并且会见\t
或者\n\t
给丢弃掉。
//参数是接收的数据的最大值
LineBasedFrameDecoder lineBasedFrameDecoder = new LineBasedFrameDecoder(100);
解码方法
@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);
}
}
下面才是真正的解码方法
这里先通过findEndOfLine方法将ByteBuf中的换行符找出来,之后就是读取一行数据(移动读指针),并且判断换行符是\n
还是\r\n
移动指针将这些换行符丢弃。
之后就是判断是否超过最大长度,如果超过就会将数据直接丢弃。
如果没有找到换行符还是会判断数据是否超过最大长度,如果超过还是会丢弃,并且会设置为丢弃模式。
如果是丢弃模式状态下找到换行符并不会将数据传递下去,而是丢弃数据并将丢弃模式取消。
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
//算出本次要截取数据的长度
final int length = eol - buffer.readerIndex();
//判断\n前面是否是\r 是返回2 不是返回1
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
//如果读取的长度>指定的最大长度
if (length > maxLength) {
//设置此缓冲区的readerIndex。 跳过这段数据
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
//判断解析的数据是否要带\r\n
if (stripDelimiter) {
//返回从当前readerIndex开始的缓冲区子区域的一个新保留的片
//返回到\r\n的有效数据 不包括\r\n
frame = buffer.readRetainedSlice(length);
//跳过\r\n
buffer.skipBytes(delimLength);
} else {
//截取到\r\n的有效数据 包括\r\n
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
} else {
//如果没有找到\r\n
final int length = buffer.readableBytes();
//如果本次数据的可读长度》最大可读长度
if (length > maxLength) {
//设置丢弃的长度为本次buffer的可读取长度
discardedBytes = length;
//跳过本次数据
buffer.readerIndex(buffer.writerIndex());
//设置为丢弃模式
discarding = true;
offset = 0;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
} else {
//找到了\r\n
if (eol >= 0) {
//以前丢弃的数据长度+本次可读的数据长度
final int length = discardedBytes + eol - buffer.readerIndex();
//拿到分隔符的长度
final int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
//跳过(丢弃)本次数据
buffer.readerIndex(eol + delimLength);
//设置丢弃数据长度为0
discardedBytes = 0;
//设置非丢弃模式
discarding = false;
if (!failFast) {
fail(ctx, length);
}
} else {
//没找到\r\n
//丢弃的数据长度+本次可读数据的长度
discardedBytes += buffer.readableBytes();
//跳过本次可读取的数据
buffer.readerIndex(buffer.writerIndex());
// 我们跳过缓冲区中的所有内容,需要再次将偏移量设置为0。
offset = 0;
}
return null;
}
}
判断缓冲区中是否有需要的换行符,有就返回行末尾的索引,没有就返回-1
/**
* 返回找到的行末尾的缓冲区中的索引。
* 如果缓冲区中没有找到行尾,则返回-1。
*/
private int findEndOfLine(final ByteBuf buffer) {
int totalLength = buffer.readableBytes();
int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
if (i >= 0) {
offset = 0;
if (i > 0 && buffer.getByte(i - 1) == '\r') {
i--;
}
} else {
offset = totalLength;
}
return i;
}