看DelimiterBasedFrameDecoder的API,有举例:
接收到的ChannelBuffer如下:
+--------------+
| ABC\nDEF\r\n |
+--------------+
经过DelimiterBasedFrameDecoder(Delimiters.lineDelimiter())之后,得到:
+-----+-----+
| ABC | DEF |
+-----+-----+
而不是
+----------+
| ABC\nDEF |
为什么 ?
首先要明确,如果不指定,DelimiterBasedFrameDecoder默认会去掉分隔符
其次看看Delimiters.lineDelimiter(),它返回两组delimiter,分别对应windows和linux的换行符
public static ChannelBuffer[] lineDelimiter() {
return new ChannelBuffer[] {
ChannelBuffers.wrappedBuffer(new byte[] { '\r', '\n' }),
ChannelBuffers.wrappedBuffer(new byte[] { '\n' }),
};
}
考察这两组分隔符
方案一
采用“\r\n”作为分隔,则返回
frameA = “ABC\nDEF”
方案二
采用“\n”返回
frameB_0 = “ABC”
frameB_1 = “DEF\r”
由于frameB_0的长度比frameA短,因此在这个例子中,会采用方案二
但有个问题,为什么不是比较全部,而是只比较frameB_0?
要知道,length(frameA) = length(frameB_0) + length(frameB_1),两者相等
刚开始,我还以为跟split一样,方案二会一次性返回“ABCDEF\r”
实际上不是
它是遇到一个分隔符,就返回一个结果
可以通过下面的代码证明:
public class ClientHandler extends SimpleChannelUpstreamHandler {
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
String msg = "ABC\nDEF\r\n";
ChannelBuffer buff = ChannelBuffers.buffer(msg.length());
buff.writeBytes(msg.getBytes());
e.getChannel().write(buff);
}
}
Server:
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
//这里设置:不删除分隔符,方便观察
pipeline.addLast("handler1", new DelimiterBasedFrameDecoder(8192, false, Delimiters.lineDelimiter()));
pipeline.addLast("handler2", new ServerStringHandler()); //打印decode后的结果
return pipeline;
}
});
ServerStringHandler:
public class ServerStringHandler extends SimpleChannelUpstreamHandler{
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
ChannelBuffer buff = (ChannelBuffer)e.getMessage();
String msg = (String)buff.toString(Helper.CHARSET_UTF8);
//String s = "abc\n"; 则msg_escape 会原样输出“abc\n”,而不是“abc”外加一个换行
String msg_escape = StringEscapeUtils.escapeJava(msg);
System.out.println("msg = " + msg_escape);
}
}
结果ServerStringHandler会分两次输出:
msg = ABC\n
msg = DEF\r\n
查看源码,会更清楚:
public class DelimiterBasedFrameDecoder extends FrameDecoder {
private final ChannelBuffer[] delimiters;
private final int maxFrameLength;
/*返回结果中,是否去掉分隔符
通常的调用是去掉分隔符,例如
new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())
等价于
new DelimiterBasedFrameDecoder(8192, /*stripDelimiter=*/true, Delimiters.lineDelimiter())
*/
private final boolean stripDelimiter;
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
// Try all delimiters and choose the delimiter which yields the shortest frame.
int minFrameLength = Integer.MAX_VALUE;
ChannelBuffer minDelim = null;
/*迭代每一个delimiter,都尝试进行decode,
然后选择返回“shortest frame”的那个delimiter
重点在indexOf这个方法
*/
for (ChannelBuffer delim: delimiters) {
int frameLength = indexOf(buffer, delim);
if (frameLength >= 0 && frameLength < minFrameLength) {
minFrameLength = frameLength;
minDelim = delim;
}
}
if (minDelim != null) {
int minDelimLength = minDelim.capacity();
ChannelBuffer frame;
if (stripDelimiter) {
frame = buffer.readBytes(minFrameLength);
buffer.skipBytes(minDelimLength);
} else {
frame = buffer.readBytes(minFrameLength + minDelimLength);
}
return frame;
}
}
/*
对frame(haystack)进行搜索,找到第一个delimiter(needle),这个位置记为i
返回 (i - haystack.readerIndex),也就是分隔后第一个sub frame的长度
可以看到,它是“找到一个,就返回一个”
*/
private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
//遍历haystack的每一个字节
for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
int haystackIndex = i;
int needleIndex;
/*haystack是否出现了delimiter,注意delimiter是一个ChannelBuffer(byte[])
例如对于haystack="ABC\r\nDEF",needle="\r\n"
那么当haystackIndex=3时,找到了“\r”,此时needleIndex=0
继续执行循环,haystackIndex++,needleIndex++,
找到了“\n”
至此,整个needle都匹配到了
程序然后执行到if (needleIndex == needle.capacity()),返回结果
*/
for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
break;
} else {
haystackIndex ++;
if (haystackIndex == haystack.writerIndex() &&
needleIndex != needle.capacity() - 1) {
return -1;
}
}
}
if (needleIndex == needle.capacity()) {
// Found the needle from the haystack!
return i - haystack.readerIndex();
}
}
return -1;
}
}
================================================
转载点别的
我们首先来看DelimiterBasedFrameDecoder的实现,个人认为这个类实现的真的很牛,有些变量的含义作者没有增加注释,有时候可能不容易猜到意图。首先我们来看一下这个类的成员变量:
- private final ChannelBuffer[] delimiters;
- private final int maxFrameLength;
- private final boolean stripDelimiter;
- private final boolean failFast;
- private boolean discardingTooLongFrame;
- private int tooLongFrameLength;
- delimiters比较好理解,应该就是这个可以接受多个分割符
- maxFrameLength这个是最大帧的length
- stripDelimiter这个也很好理解,是否跳过分隔符,就是最终解码的数据里面是否包含分隔符
- failFast 为true是说发现读到的数据已经超过了maxFrameLength了,立即报TooLongFrameException,如果为false就是读完整个帧数据后再报
- discardingTooLongFrame 这个是最难理解的,含义是当前的解码器是否处于discardingTooLongFrame状态,这个参数最容易理解为是否丢弃tooLongFrame,这个是一个标志位,在构造函数里面是不能进行设置的,只能是解码器进行设置
- tooLongFrameLength这个也是一个状态属性,就是说出现了超长帧了,哪这个帧的长度到底是多少,就是这个长度,一般来说是在发现当前buffer的可读数据超过最大帧时候进行设置
好,看完这个东东后我们就来看一下它的实现:
我们就来看一下最长的这个构造函数吧:
- public DelimiterBasedFrameDecoder(
- int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) {
- validateMaxFrameLength(maxFrameLength);
- if (delimiters == null) {
- throw new NullPointerException("delimiters");
- }
- if (delimiters.length == 0) {
- throw new IllegalArgumentException("empty delimiters");
- }
- this.delimiters = new ChannelBuffer[delimiters.length];
- for (int i = 0; i < delimiters.length; i ++) {
- ChannelBuffer d = delimiters[i];
- validateDelimiter(d);
- this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
- }
- this.maxFrameLength = maxFrameLength;
- this.stripDelimiter = stripDelimiter;
- this.failFast = failFast;
- }
这个里面我们发现就是如果传递多个delimiter的时候,在这个进行了一个slice操作,没有什么特别的。
下来我们来看一下最关键的decode方法吧:
- @Override
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
- // Try all delimiters and choose the delimiter which yields the shortest frame.
- int minFrameLength = Integer.MAX_VALUE;
- ChannelBuffer minDelim = null;
- for (ChannelBuffer delim: delimiters) {
- int frameLength = indexOf(buffer, delim);
- if (frameLength >= 0 && frameLength < minFrameLength) {
- minFrameLength = frameLength;
- minDelim = delim;
- }
- }
- if (minDelim != null) {
- int minDelimLength = minDelim.capacity();
- ChannelBuffer 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(ctx, tooLongFrameLength);
- }
- return null;
- }
- if (minFrameLength > maxFrameLength) {
- // Discard read frame.
- buffer.skipBytes(minFrameLength + minDelimLength);
- fail(ctx, minFrameLength);
- return null;
- }
- if (stripDelimiter) {
- frame = buffer.readBytes(minFrameLength);
- buffer.skipBytes(minDelimLength);
- } else {
- frame = buffer.readBytes(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(ctx, tooLongFrameLength);
- }
- }
- } else {
- // Still discarding the buffer since a delimiter is not found.
- tooLongFrameLength += buffer.readableBytes();
- buffer.skipBytes(buffer.readableBytes());
- }
- return null;
- }
- }
我们慢慢的来看这个代码的实现,作者为了实现failfast做了很多努力。首先我们看到最开始的代码就发现,最上面实际上是尝试所有的分隔符,然后找出一个可用将帧分割最小的一个分割符出来,下面就是if和else,我们先来看if的逻辑:
- 如果找到了分割符,如果当前的解码器处于discardingTooLongFrame状态,也就是说上次解码的时候发现了超长帧,被抛弃过。首先将这个状态修改过来,标志为不是抛弃过超长帧,这个时候将整个帧丢弃掉,然后如果不是failfast状态,抛出异常,这个怎么理解呢,可以理解为上次在读取的时候发现了超长帧,但是由于设置了不立即抛出异常,而是等读完整个帧数据才抛出异常,这个时候既然发现了分隔符,该到抛出异常的时候了,最后return null表明此次分帧是失败状态。
- 如果发现此次的帧数据超过最大帧的长度,直接抛出异常
- 最后就是如果跳过分隔符,就直接跳过,负责就和分隔符和帧的实际数据一块返回
else的逻辑是当前读到的数据没有发现分隔符的情况下的逻辑,我们来看下:
- 如果发现当前的解码器不是处于discardingTooLongFrame状态,当前buffer里面的可读数据又比最大帧要大,我们就将解码器标记为discardingTooLongFrame状态,并设置这个超长帧的大小,如果是failfast状态,就立马抛出异常,也就是说我们发现了超长帧了,所以我们立马抛出异常
- 如果发现当前的解码器已经处于discardingTooLongFrame状态,我们别无他方,只能修改下tooLongFrameLength的长度,然后听天由命,等待下次解码操作
- 这个时候如果一旦发现了超长帧,都return null,含义就是说此次解码是无效的
最后我们来看一下indexOf的实现吧,这个很简单,其实思想和在字符串中找子串的思想是一致的,就不多讲解了,自己上代码:
- private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
- for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
- int haystackIndex = i;
- int needleIndex;
- for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
- if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
- break;
- } else {
- haystackIndex ++;
- if (haystackIndex == haystack.writerIndex() &&
- needleIndex != needle.capacity() - 1) {
- return -1;
- }
- }
- }
- if (needleIndex == needle.capacity()) {
- // Found the needle from the haystack!
- return i - haystack.readerIndex();
- }
- }
- return -1;
- }