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;
    }

}
时间: 2024-08-29 12:23:19

NETTY源码学习-DELIMITERBASEDFRAMEDECODER的相关文章

Netty源码学习——Included transports(传输方式)

Transport API的核心: Channel接口 类图表示Channel含有Pipeline和Config接口,pipeline上一节有所介绍. Channel是线程安全的,这表示在多线环境下操作同一个Channel,不会有数据问题. final Channel channel = null; final ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8); // #1 Runnable writ

Netty源码学习——ChannelPipeline模型分析

参考Netty API io.netty.channel.ChannelPipeline A list of ChannelHandlers which handles or intercepts inbound events and outbount operations of aChannel.ChannelPipeline implements an advanced form of theIntercepting Filter pattern to give a user full co

Netty源码学习——EventLoopGroup原理:NioEventLoopGroup分析

类结构图: 不了解Executor接口原理的可以查看concurrent包中的api介绍,这里只介绍Netty中EventExecutorGroup的主要功能! 从类的结构图中可以看到EventExecutorGroup是直接继承ScheduledExecutorService这个接口的,为了说明白Group的原理这里顺便提一下ScheduledExecutorService的用途! java.util.concurrent.ScheduledExecutorService An Executo

Netty源码学习(六)ChannelPipeline

0. ChannelPipeline简介 ChannelPipeline = Channel + Pipeline,也就是说首先它与Channel绑定,然后它是起到类似于管道的作用:字节流在ChannelPipeline上流动,流动的过程中被ChannelHandler修饰,最终输出. 1. ChannelPipeline类图 ChannelPipeline只有两个子类,直接一起放上来好了,其中EmbeddedChannelPipeline主要用于测试,本文只介绍DefaultChannelPi

(三)Netty源码学习笔记之boss线程处理流程

尊重原创,转载注明出处,原文地址:http://www.cnblogs.com/cishengchongyan/p/6160194.html  本文我们将先从NioEventLoop开始来学习服务端的处理流程.话不多说,开始学习~~~~ 我们从上文中已经知道server在启动的时候会开启两个线程:bossGroup和workerGroup,这两个线程分别是boss线程池(用于接收client请求)和worker线程池(用于处理具体的读写操作),这两个线程调度器都是NioEventLoopGrou

Netty源码学习(二)NioEventLoopGroup

0. NioEventLoopGroup简介 NioEventLoopGroup可以理解为一个线程池,内部维护了一组线程,每个线程负责处理多个Channel上的事件,而一个Channel只对应于一个线程,这样可以回避多线程下的数据同步问题. 1. NioEventLoopGroup类图 2. 构造方法 new NioEventLoopGroup()方法会调用到MultithreadEventLoopGroup的构造方法: private static final int DEFAULT_EVEN

Netty源码学习(五)ChannelInitializer

0. ChannelInitializer简介 直接用ChannelInitializer的注释吧:A special ChannelInboundHandler which offers an easy way to initialize a Channel once it was registered to its eventLoop. 1. ChannelInitializer类图 需要注意的是: a. ChannelInitializer继承于ChannelInboundHandler接

【转】Netty源码的阅读学习

Netty 源码阅读学习 博客分类: java Netty 背景  最忌工作中接触到Netty相关应用场景,之前看过mima的部分源码,所以最近看了Netty的部分源码和学习其设计思想,做个简单的分享(学习代码为:Netty:3.6.3.FINALE). Netty概述 官方:Netty is an asynchronous event-driven network application framework for rapid development of maintainable high

Netty源码解读(一)概述

Netty和Mina是Java世界非常知名的通讯框架.它们都出自同一个作者,Mina诞生略早,属于Apache基金会,而Netty开始在Jboss名下,后来出来自立门户netty.io.关于Mina已有@FrankHui的Mina系列文章,我正好最近也要做一些网络方面的开发,就研究一下Netty的源码,顺便分享出来了. Netty目前有两个分支:4.x和3.x.4.0分支重写了很多东西,并对项目进行了分包,规模比较庞大,入手会困难一些,而3.x版本则已经被广泛使用.本系列文章针对netty 3.