Netty网络编程五:Netty粘包拆包解码器源码分析之自定义解码器
一:概述
由上一篇文章讲完了ByteToMessageDecoder,再来看其实现LineBasedFrameDecoder ,DelimiterBasedFrameDecoder,FixedLengthFrameDecoder发现只要实现decode()方法即可,并按照指定的分割条件进行字节流数据分割,然后将处理好的数据加入到out的集合中即可。
二:Netty解码器源码
1) 先看FixedLengthFrameDecoder解码器,这事一个固定长度的解码器
这个解码器源码比较简单
是继承ByteToMessageDecoder,实现抽象方法decode,
注意: 这个decode方法的第二个参数in ,就是ByteToMessageDecode中的缓冲累计器,即存放所有累计的缓冲数据 ,也就是
ByteBuf cumulation;
private Cumulator cumulator = MERGE_CUMULATOR;
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 1.解码数据
Object decoded = decode(ctx, in);
if (decoded != null) {
// 3.通过 把解码出来的数据交给out集合中即可
out.add(decoded);
}
}
具体解码实现:
protected Object decode(
@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
// 2. 解码
// 缓冲容器中的可读数据小于长度,说明数据不足
if (in.readableBytes() < frameLength) {
return null;
} else {
//返回frameLengt长度的数据,并更新in缓冲中的readerIndex()索引位置
return in.readRetainedSlice(frameLength);
}
}
2) LineBasedFrameDecoder 解码器,就是安装"\n" 或"\r\n"进行分割的解码器,也就是回车换行分隔符
一般我们在使用这个解码器时,会先传一个长度的构造参数,表名最大解码长度,超过这个长度没解码处理一条数据,则抛出异常,这也是避免内存溢出
public LineBasedFrameDecoder(final int maxLength) {
this(maxLength, true, false);
}
同样,看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);
}
}
也是同样的处理思路, 看下decode(ctx, in)的实现:
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
// eol 表示
final int eol = findEndOfLine(buffer);
// discarding表示是否超过指定的maxLength,第一次进来为false
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame; // 要返回的解码后的对象
final int length = eol - buffer.readerIndex(); // 这个要返回的对下的长度
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1; //分隔符长度, '\n' 还是'\r\n'
if (length > maxLength) { // 超出长度,抛出异常
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
if (stripDelimiter) {// stripDelimiter表示解码时是否跳过分隔符,默认true
frame = buffer.readRetainedSlice(length); // 读对象
buffer.skipBytes(delimLength); //跳过分隔符
} else {
//不跳过分隔符情况
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;// 返回解析处理的对象
} else { // 可读数据超过maxLength 抛弃异常数据
final int length = buffer.readableBytes();
if (length > maxLength) {
discardedBytes = length;
buffer.readerIndex(buffer.writerIndex());
discarding = true;
offset = 0;
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());
// We skip everything in the buffer, we need to set the offset to 0 again.
offset = 0;
}
return null;
}
}
看下findEndOfLine(buffer);方法获取要解码数据的末尾的一条索引位置,及找到下一个分隔符位置
/**
* 返回找到的行末尾的缓冲区中的索引。如果缓冲区中没有找到行尾,则返回-1。
*/
private int findEndOfLine(final ByteBuf buffer) {
// 缓冲中带读取数据总长度
int totalLength = buffer.readableBytes();
// 遍历缓冲,找到'\n'符号的位置索引
int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
// 索引找找后,看是否这个索引位置前一位时'\r'(不同操作系统换行符区别),然后返回索引位置
if (i >= 0) {
offset = 0;
if (i > 0 && buffer.getByte(i - 1) == '\r') {
i--;
}
} else {
offset = totalLength;
}
return i;
}
3) DelimiterBasedFrameDecoder指定分割符解码器
@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);
}
}
查看decode(ctx, in)
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
if (lineBasedDecoder != null) {// 指定为 LineBasedFrameDecoder解码器
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;
}
}
综上,所有的解码器都是一致的, 即继承ByteToMessageDecoder接口,实现 decode(ChannelHandlerContext ctx, ByteBuf in, List out)方法, 最后把解码出来的对象存放在out集合中即可
三:自定义解码器
自定义一个解码器,也如同上面总结的那样,继承ByteToMessageDecoder接口,实现 decode(ChannelHandlerContext ctx, ByteBuf in, List out)方法即可.
文章中案例代码:https://download.csdn.net/download/qq_22871607/11072379