NETTY源码学习-DELIMITERBASEDFRAMEDECODER

看DelimiterBasedFrameDecoder的API,有举例:

接收到的ChannelBuffer如下:

 +--------------+
 | ABC\nDEF\r\n |
 +--------------+

经过DelimiterBasedFrameDecoder(Delimiters.lineDelimiter())之后,得到:

 +-----+-----+
 | ABC | DEF |
 +-----+-----+

而不是

 +----------+
 | ABC\nDEF |

为什么 ?

首先要明确,如果不指定,DelimiterBasedFrameDecoder默认会去掉分隔符

其次看看Delimiters.lineDelimiter(),它返回两组delimiter,分别对应windowslinux的换行符

   

 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的实现,个人认为这个类实现的真的很牛,有些变量的含义作者没有增加注释,有时候可能不容易猜到意图。首先我们来看一下这个类的成员变量:

 

  1. private final ChannelBuffer[] delimiters;  
  2.    private final int maxFrameLength;  
  3.    private final boolean stripDelimiter;  
  4.    private final boolean failFast;  
  5.    private boolean discardingTooLongFrame;  
  6.    private int tooLongFrameLength;  

 

  • delimiters比较好理解,应该就是这个可以接受多个分割符
  • maxFrameLength这个是最大帧的length
  • stripDelimiter这个也很好理解,是否跳过分隔符,就是最终解码的数据里面是否包含分隔符
  • failFast 为true是说发现读到的数据已经超过了maxFrameLength了,立即报TooLongFrameException,如果为false就是读完整个帧数据后再报
  • discardingTooLongFrame 这个是最难理解的,含义是当前的解码器是否处于discardingTooLongFrame状态,这个参数最容易理解为是否丢弃tooLongFrame,这个是一个标志位,在构造函数里面是不能进行设置的,只能是解码器进行设置
  • tooLongFrameLength这个也是一个状态属性,就是说出现了超长帧了,哪这个帧的长度到底是多少,就是这个长度,一般来说是在发现当前buffer的可读数据超过最大帧时候进行设置

        好,看完这个东东后我们就来看一下它的实现:

       我们就来看一下最长的这个构造函数吧:

        

  1. public DelimiterBasedFrameDecoder(  
  2.            int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) {  
  3.        validateMaxFrameLength(maxFrameLength);  
  4.        if (delimiters == null) {  
  5.            throw new NullPointerException("delimiters");  
  6.        }  
  7.        if (delimiters.length == 0) {  
  8.            throw new IllegalArgumentException("empty delimiters");  
  9.        }  
  10.        this.delimiters = new ChannelBuffer[delimiters.length];  
  11.        for (int i = 0; i < delimiters.length; i ++) {  
  12.            ChannelBuffer d = delimiters[i];  
  13.            validateDelimiter(d);  
  14.            this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());  
  15.        }  
  16.        this.maxFrameLength = maxFrameLength;  
  17.        this.stripDelimiter = stripDelimiter;  
  18.        this.failFast = failFast;  
  19.    }  

        这个里面我们发现就是如果传递多个delimiter的时候,在这个进行了一个slice操作,没有什么特别的。

       下来我们来看一下最关键的decode方法吧:

        

  1. @Override  
  2.     protected Object decode(  
  3.             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {  
  4.         // Try all delimiters and choose the delimiter which yields the shortest frame.  
  5.         int minFrameLength = Integer.MAX_VALUE;  
  6.         ChannelBuffer minDelim = null;  
  7.         for (ChannelBuffer delim: delimiters) {  
  8.             int frameLength = indexOf(buffer, delim);  
  9.             if (frameLength >= 0 && frameLength < minFrameLength) {  
  10.                 minFrameLength = frameLength;  
  11.                 minDelim = delim;  
  12.             }  
  13.         }  
  14.   
  15.         if (minDelim != null) {  
  16.             int minDelimLength = minDelim.capacity();  
  17.             ChannelBuffer frame;  
  18.   
  19.             if (discardingTooLongFrame) {  
  20.                 // We've just finished discarding a very large frame.  
  21.                 // Go back to the initial state.  
  22.                 discardingTooLongFrame = false;  
  23.                 buffer.skipBytes(minFrameLength + minDelimLength);  
  24.   
  25.                 int tooLongFrameLength = this.tooLongFrameLength;  
  26.                 this.tooLongFrameLength = 0;  
  27.                 if (!failFast) {  
  28.                     fail(ctx, tooLongFrameLength);  
  29.                 }  
  30.                 return null;  
  31.             }  
  32.   
  33.             if (minFrameLength > maxFrameLength) {  
  34.                 // Discard read frame.  
  35.                 buffer.skipBytes(minFrameLength + minDelimLength);  
  36.                 fail(ctx, minFrameLength);  
  37.                 return null;  
  38.             }  
  39.   
  40.             if (stripDelimiter) {  
  41.                 frame = buffer.readBytes(minFrameLength);  
  42.                 buffer.skipBytes(minDelimLength);  
  43.             } else {  
  44.                 frame = buffer.readBytes(minFrameLength + minDelimLength);  
  45.             }  
  46.   
  47.             return frame;  
  48.         } else {  
  49.             if (!discardingTooLongFrame) {  
  50.                 if (buffer.readableBytes() > maxFrameLength) {  
  51.                     // Discard the content of the buffer until a delimiter is found.  
  52.                     tooLongFrameLength = buffer.readableBytes();  
  53.                     buffer.skipBytes(buffer.readableBytes());  
  54.                     discardingTooLongFrame = true;  
  55.                     if (failFast) {  
  56.                         fail(ctx, tooLongFrameLength);  
  57.                     }  
  58.                 }  
  59.             } else {  
  60.                 // Still discarding the buffer since a delimiter is not found.  
  61.                 tooLongFrameLength += buffer.readableBytes();  
  62.                 buffer.skipBytes(buffer.readableBytes());  
  63.             }  
  64.             return null;  
  65.         }  
  66.     }  

          我们慢慢的来看这个代码的实现,作者为了实现failfast做了很多努力。首先我们看到最开始的代码就发现,最上面实际上是尝试所有的分隔符,然后找出一个可用将帧分割最小的一个分割符出来,下面就是if和else,我们先来看if的逻辑:

  •   如果找到了分割符,如果当前的解码器处于discardingTooLongFrame状态,也就是说上次解码的时候发现了超长帧,被抛弃过。首先将这个状态修改过来,标志为不是抛弃过超长帧,这个时候将整个帧丢弃掉,然后如果不是failfast状态,抛出异常,这个怎么理解呢,可以理解为上次在读取的时候发现了超长帧,但是由于设置了不立即抛出异常,而是等读完整个帧数据才抛出异常,这个时候既然发现了分隔符,该到抛出异常的时候了,最后return null表明此次分帧是失败状态。
  • 如果发现此次的帧数据超过最大帧的长度,直接抛出异常
  • 最后就是如果跳过分隔符,就直接跳过,负责就和分隔符和帧的实际数据一块返回

      else的逻辑是当前读到的数据没有发现分隔符的情况下的逻辑,我们来看下:

  •  如果发现当前的解码器不是处于discardingTooLongFrame状态,当前buffer里面的可读数据又比最大帧要大,我们就将解码器标记为discardingTooLongFrame状态,并设置这个超长帧的大小,如果是failfast状态,就立马抛出异常,也就是说我们发现了超长帧了,所以我们立马抛出异常
  • 如果发现当前的解码器已经处于discardingTooLongFrame状态,我们别无他方,只能修改下tooLongFrameLength的长度,然后听天由命,等待下次解码操作
  • 这个时候如果一旦发现了超长帧,都return null,含义就是说此次解码是无效的

        最后我们来看一下indexOf的实现吧,这个很简单,其实思想和在字符串中找子串的思想是一致的,就不多讲解了,自己上代码:

         

  1. private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {  
  2.         for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {  
  3.             int haystackIndex = i;  
  4.             int needleIndex;  
  5.             for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {  
  6.                 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {  
  7.                     break;  
  8.                 } else {  
  9.                     haystackIndex ++;  
  10.                     if (haystackIndex == haystack.writerIndex() &&  
  11.                         needleIndex != needle.capacity() - 1) {  
  12.                         return -1;  
  13.                     }  
  14.                 }  
  15.             }  
  16.   
  17.             if (needleIndex == needle.capacity()) {  
  18.                 // Found the needle from the haystack!  
  19.                 return i - haystack.readerIndex();  
  20.             }  
  21.         }  
  22.         return -1;  
  23.     }  

转载于:https://www.cnblogs.com/gehonglin/p/4174147.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值