首页 > 其他 > 详细

Tomcat源码阅读之底层IO封装(1)InternalNioInputBuffer的分析

时间:2014-03-29 05:10:07      阅读:523      评论:0      收藏:0      [点我收藏+]

最近读代码的节奏很慢,。。在HttpPrcoessor部分已经停滞了挺久的时间了。。

可能它设计到的东西还是挺多的吧。。。

今天写的是InternalNioInputBuffer类型的分析,它算是Tomcat对IO封装部分的一个十分重要的环节了,可以将其理解为用于沟通底层socket与上层servletInputStream的中间层。。。而且InternalNioInputBuffer中还实现了HTTP协议的requestLine与header部分的解析。。。

嗯,这里先来看看简单的继承结构吧:

bubuko.com,布布扣


这个够简单的了吧,这里先来看看最上层的接口InputBuffer的定义吧:

public interface InputBuffer {


    /** Return from the input stream.
        IMPORTANT: the current model assumes that the protocol will ‘own‘ the
        buffer and return a pointer to it in ByteChunk ( i.e. the param will
        have chunk.getBytes()==null before call, and the result after the call ).
    */
	//从与这个request关联的socket读取数据保存到chunk里面
    public int doRead(ByteChunk chunk, Request request)
        throws IOException;


}

嗯,也非常的简单,就只有一个方法,doRead方法,用于读取数据,并将数据保存到chunk里面。。。


好啦,接下来就是AbstractInputBuffer的定义了,这里先来看看的一些重要的属性的申明吧:

    protected Request request;  //与当前buffer关联的tomcat的request

    protected MimeHeaders headers;  //用于保存所有的处理出来的header数据

    protected boolean parsingHeader;     //是否正在处理header

    protected boolean swallowInput;  //是否要接收当前关联的连接的数据

    protected byte[] buf;   //用于存数据的字节数组

    protected int lastValid;  //表示最后一个可用数据的下标

    protected int pos;  //当前读取到的下标

    protected int end;  //用于指向header在buffer的尾部的下标

    protected InputBuffer inputStreamInputBuffer;   //这里内部有一个InputBuffer,用于代理当前对象的一些方法,主要是暴露给外部的读取数据的api

    protected InputFilter[] filterLibrary;  //所有的filter

    protected InputFilter[] activeFilters;   //活动的filter,当外部代码调用buffer来读取数据的时候,如果有filter,那么将会用filter来处理一下

    protected int lastActiveFilter;   //最后一个活动的filter的下标

嗯,这里属性的定义还不算太多吧,不过都挺重要的,具体的每个属性是什么意思,后面的注释应该也说的比较清楚了。。。而且可以看到,具体的数据的保存其实也是保存到一个字节数组里面的。。。

另外这里可以看到定义了一个InputBuffer类型的属性inputStreamInputBuffer,这里大概可以理解为一个外观模式吧,通过这个对象,具体的向代理当前对象的一些方法,用于暴露出doRead方法,来看看在AbstractInputBuffer中定义的doRead方法吧:

//读取数据的方法
    public int doRead(ByteChunk chunk, Request req)
        throws IOException {

        if (lastActiveFilter == -1)  //如果都没有活动的filter,anemia可以直接读取
            return inputStreamInputBuffer.doRead(chunk, req);
        else  //否则需要filter介入
            return activeFilters[lastActiveFilter].doRead(chunk,req);

    }

嗯,这里也可以知道filter是干嘛用的了,这里如果有filter的话,那么会在filter上面调用doRead方法,这样可以在具体的读取的数据的时候做一些额外的工作,例如context-length的filter,就用于限制读取的数据的数量。。。

当然最终还是调用inputStreamInputBuffer来读取数据的。。具体的会在InternalNioInputBuffer中交代.

好了,接下来再来看看InternalNioInputBuffer类型,还是先来看看它的属性定义吧:

 private NioChannel socket;  //关联的底层的niochannel

    private NioSelectorPool pool;  //提供selector的pool,它用于读取http请求的body数据,因为必须要阻塞的读取,所以为了节约CPU,就只有用selector来协作了

    private final int headerBufferSize;  //允许的最大的hader大小

    private int socketReadBufferSize;   //下面的channel的设置的读取的buffer的size

首先是底层关联的nioChannel,然后就还有一些配置。。这里再来看看它的构造函数吧:

    //构造函数,第一个参数是关联的request对象,第二个参数是最大的header数据限制
    public InternalNioInputBuffer(Request request, int headerBufferSize) {

        this.request = request;
        headers = request.getMimeHeaders();  //用于保存header

        this.headerBufferSize = headerBufferSize;

        inputStreamInputBuffer = new SocketInputBuffer();  //真正用到的buffer,其实只不过是为了实现将当前底层字节数组里面的数据暴露出去,doRead方法,将数据写到某个chunk

        filterLibrary = new InputFilter[0];  //input的filter吧
        activeFilters = new InputFilter[0];   //活动的filter,例如content-length什么的限制
        lastActiveFilter = -1;  //刚开始直接设置为-1,表示低下的字节数组没有数据

        //一些标志位的初始化
        parsingHeader = true;   //表示还要解析header
        parsingRequestLine = true;
        parsingRequestLinePhase = 0;
        parsingRequestLineEol = false;
        parsingRequestLineStart = 0;
        parsingRequestLineQPos = -1;
        headerParsePos = HeaderParsePosition.HEADER_START;
        headerData.recycle();
        swallowInput = true;   //默认是ture的,表示还要接受当前连接的数据

其实这里主要是一些标志位的初始化,另外这里还有一个比较重要的这里为inputStreamInputBuffer属性初始化了对象,是SocketInputBuffer类型的,它是一个当前类型的内部类。。。来看看它的定义吧:

    //这里实现了一个简单的InputBuffer,主要是为了代理当前对象的读取数据的方法,将方法暴露给外面的对象
    protected class SocketInputBuffer
        implements InputBuffer {


        /**
         * Read bytes into the specified chunk.
         */
        @Override
        public int doRead(ByteChunk chunk, Request req )
            throws IOException {

            if (pos >= lastValid) { //如果低下的字节数组已经没有数据可以读取了,这里主要是调用fill方法,从socket里面读取数据,并将其保存到当前对象的字节数组
                if (!fill(true,true)) //这里是阻塞的读取//read body, must be blocking, as the thread is inside the app
                    return -1;
            }

            int length = lastValid - pos;  //表示还有多少数据可以读取
            chunk.setBytes(buf, pos, length);  //相当有从底层的字节数据中读数据,这里会重用下面的byte数组
            pos = lastValid;  //更新pos的下标

            return (length);
        }
    }

嗯,这里应该就能够明白为啥前面说是为了代理当前对象的方法,用于暴露给外部doRead方法了吧,因为它的具体实现还是通过InternalNioInputBuffer的方法来实现的。。。这里说白了就是将当前对象的字节数组buf设置到chunk里面去,也算是数据的重用吧,然后再设置下标就好了。。。

具体的读取数据的方法则是通过fill方法来实现的。。。来看看吧:

    //读取数据到缓冲,在nio里,第一个参数一般是ture,第二个一般是false,但是在读取body的时候则必须是阻塞的
    protected boolean fill(boolean timeout, boolean block)
            throws IOException, EOFException {

        boolean read = false;

        if (parsingHeader) {  //如果是正在处理header的极端,那么这里可能要判断是否超出最大值

            if (lastValid > headerBufferSize) {
                throw new IllegalArgumentException
                    (sm.getString("iib.requestheadertoolarge.error"));
            }

            // Do a simple read with a short timeout
            read = readSocket(timeout,block)>0;
        } else {  //body的数据吧
            lastValid = pos = end;
            // Do a simple read with a short timeout
            read = readSocket(timeout, block)>0;  //从socket读取数据
        }
        return read;
    }

嗯,这里读取的方法有阻塞的方式,也有非阻塞的方法吧,由于现在tomcat都是用nio的方法,所以在处理http请求的requestline与header的时候都是非阻塞的,但是在处理请求的body的时候则必须是阻塞的了,因为读取body部分的代码一般是在servlet部分控制的也就是由上层代码控制,这个就已经超出了tomcat控制的范围、。、、、

再来看看readSocket方法吧:

    //从底层的socket读取数据
    private int readSocket(boolean timeout, boolean block) throws IOException {
        int nRead = 0;
        socket.getBufHandler().getReadBuffer().clear();  //调用socket关联的nio的buf的clear方法,待会可以将socket里读取的数据写进去了
        if ( block ) {  // 如果是阻塞的话将会后去selector来关联当前的socket,为节约cpu吧,在读取body的时候就会是阻塞的读取
            Selector selector = null;
            try {
                selector = pool.get();  //从selector的pool里面获取一个selector对象
            } catch ( IOException x ) {
                // Ignore
            }
            try {
                NioEndpoint.KeyAttachment att =
                        (NioEndpoint.KeyAttachment) socket.getAttachment(false);
                if (att == null) {
                    throw new IOException("Key must be cancelled.");
                }
                nRead = pool.read(socket.getBufHandler().getReadBuffer(),
                        socket, selector,
                        socket.getIOChannel().socket().getSoTimeout());
            } catch ( EOFException eof ) {
                nRead = -1;
            } finally {
                if ( selector != null ) pool.put(selector);
            }
        } else { //这里是非阻塞的读取数据,有多少就读多少就好了
            nRead = socket.read(socket.getBufHandler().getReadBuffer());
        }
        if (nRead > 0) {  //如果有数据读取出来了
            socket.getBufHandler().getReadBuffer().flip();  //调用flip,待会可以从buf里读取数据了
            socket.getBufHandler().getReadBuffer().limit(nRead);  //设置limit参数
            expand(nRead + pos);  //这里会检查低下的字节数据是否够大,如果不够大的话,会扩大
            socket.getBufHandler().getReadBuffer().get(buf, pos, nRead);  //将socket读取的数据转移到当前对象使用的buf,也就是创建的字节数组
            lastValid = pos + nRead;  //更新最后一个有效数据的下标  更新lastValid下标
            return nRead;  //返回读取的数据量
        } else if (nRead == -1) {  //如果是-1的话,那么表示当前出错了
            //return false;
            throw new EOFException(sm.getString("iib.eof.error"));
        } else {
            return 0;
        }
    }

其实说白了就是通过底层的channel来读取数据,然后再将读取到的数据从channel的buf里面拷贝到当前对象的字节数组buf里面就好了。。。这里多了一层拷贝,不知道是不是应该优化一下呀。。。

好了,到这里整个InternalNioInputBuffer类型如何通过底层channel读取数据应该很清楚了吧,另外它是如何暴露给外面doRead方法应该也算清楚了。。。。

当然这并不是InternalNioInputBuffer类型的全部,因为其实它还会具体的负责http协议的处理,有如下方法:

(1)parseRequestLine,用于解析http的requestLine,

(2)parseHeaders,用于解析header部分。。。


嗯,最后再来总结一下InternalNioInputBuffer与httpprocessor对象的关系吧。。。

    //构造函数,这里主要是创建buffer对象
    // 第一个参数是最大的httpheader数量可以有多少,第二个是用到的endpoint,第三个与第四个参数主要是对数据传输filter的设置
    public Http11NioProcessor(int maxHttpHeaderSize, NioEndpoint endpoint,
            int maxTrailerSize, int maxExtensionSize) {

        super(endpoint);

        inputBuffer = new InternalNioInputBuffer(request, maxHttpHeaderSize);  //创建inputbuffer
        request.setInputBuffer(inputBuffer);   //在request设置inputbuffer

        outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);  // 创建outputbuffer
        response.setOutputBuffer(outputBuffer);  //在response设置outputbuffer

        initializeFilters(maxTrailerSize, maxExtensionSize);  //初始化input与output的filter
    }

在httpprocessor对象创建的时候就会相应的创建InternalNioInputBuffer对象,并且设置相应的属性,

然后在每次process方法的时候,会首先执行如下代码:

 getInputBuffer().init(socketWrapper, endpoint);  //初始化buffer,这里主要是为了将inputbuf底层用的socket与当前要处理的socket关联起来

InternalNioInputBuffer对象初始化,例如设置底层的channel,用到的selectorPool等:

    //在httpprocessor里面用到的inputbuffer的初始化方法,这里书要是为了保存关联的socket以及一些数据大小限制参数的设置
    protected void init(SocketWrapper<NioChannel> socketWrapper,
            AbstractEndpoint<NioChannel> endpoint) throws IOException {

        socket = socketWrapper.getSocket();  //首先获取底层的channel
        if (socket == null) {
            // Socket has been closed in another thread
            throw new IOException(sm.getString("iib.socketClosed"));
        }
        socketReadBufferSize =
            socket.getBufHandler().getReadBuffer().capacity();  //获取buffer的容量大小

        int bufLength = headerBufferSize + socketReadBufferSize;  //构建要创建的长度
        if (buf == null || buf.length < bufLength) {
            buf = new byte[bufLength];   //构建字节数组
        }

        pool = ((NioEndpoint)endpoint).getSelectorPool();    //selector的pool
    }

然后就还有调用parseRequestLine与parseHeaders方法来处理http请求。。


最后他还剩下的工作,就是暴露给外部doRead方法,用于上层对象获取底层channel接收到的数据。。。

Tomcat源码阅读之底层IO封装(1)InternalNioInputBuffer的分析,布布扣,bubuko.com

Tomcat源码阅读之底层IO封装(1)InternalNioInputBuffer的分析

原文:http://blog.csdn.net/fjslovejhl/article/details/22403917

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!