使用Netty实现的tcp服务端,由于tcp是流式传输的,故需要选用一个解码器对流式消息进行解码和包分隔,以防收到不正确的包。例如LineBasedFrameDecoder,LengthFieldBasedFrameDecoder,DelimiterBasedFrameDecoder等常用解码器。
++需求是收到的消息最长长度限制在50M内,且没有固定的消息头,每次接收消息又不一定在结尾存在固定的换行符,所以现有的解码器都不足够满足需求。++
LineBasedFrameDecoder在收到数据后累加在内存变量中,然后检测到固定的换行符时将消息截断并发给下一个handler。但如果一直没有收到换行符时,则会在channelinactive时,丢弃之前接收到的所有消息。但实际上他会调用一次decodeLast方法,再执行一次decode逻辑。为了解决这个问题,可以通过自定义子类覆盖其decodeLast方法,实现自定义的逻辑。
为了解决数据丢弃问题,该解码器在执行到此方法时,检测最后一个字节是不是换行符,如果不是换行符则手动增加一个换行符到其中,此时就肯定会被decode方法将之前接收到的数据组成一个包。
java
/**
* 基于换行符的自定义解码器
* 解决了当流传输结束时,最后一个字符不是换行符无法解析的问题
* 当最后一个字符不是换行符时,增加一个换行符
*/
private static class CustomLineBasedFrameDecoder extends LineBasedFrameDecoder {
private static final byte LINE_DELIMITER = '\n';
public CustomLineBasedFrameDecoder(int maxLength) {
super(maxLength);
}
public CustomLineBasedFrameDecoder(int maxLength, boolean stripDelimiter, boolean failFast) {
super(maxLength, stripDelimiter, failFast);
}
@Override
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.isReadable()) {
//查看最后一个字节是不是换行符 如果不是则增加一个换行符
byte lastByte = in.getByte(in.writerIndex() - 1);
if (lastByte != LINE_DELIMITER) {
if (in.isWritable()) {
in.writeByte(LINE_DELIMITER);
} else {
//未测试代码块
ByteBuf old = in;
//如果不可写 则重新分配一个缓冲区
in = MERGE_CUMULATOR.cumulate(ctx.alloc(), internalBuffer(), Unpooled.buffer(1).writeByte(LINE_DELIMITER));
old.clear();
}
}
}
super.decodeLast(ctx, in, out);
}
}