您的位置:首页 > 运维架构 > Tomcat

tomcat请求处理分析(五) 请求到响应流

2017-08-19 00:02 399 查看

1.1.1.1  请求到响应界面流

请求处理的过程主要是将所有的东西解析成流,转化成对应的http报文,所以在这里我先不关注servlet因为它最终也就是解析成流里面的数据

processKey里面最终执行的是processSocket,它是线从缓存中获取对应的线程池,没有的话就创建一个,然后进行执行

protected boolean processSocket(KeyAttachmentattachment,
SocketStatus status, boolean
dispatch) {

    try {

        if (attachment==
null) {

            return false;

        }

        SocketProcessor sc = processorCache.pop();

        if ( sc == null
) sc = new
SocketProcessor(attachment,
status);

        else sc.reset(attachment,
status);

        Executor executor =getExecutor();

        if (dispatch &&executor != null) {

            executor.execute(sc);

        } else
{

            sc.run();

        }

    } catch (RejectedExecutionExceptionree) {

        log.warn(sm.getString("endpoint.executor.fail",
attachment.getSocket()),
ree);

        return false;

    } catch
(Throwablet) {

        ExceptionUtils.handleThrowable(t);

        // This means we got anOOM or similar creating a thread, or that

        // the pool and its queue arefull

        log.error(sm.getString("endpoint.process.fail"),
t);

        return false;

    }

    return true;

}
    在上面描述的线程中,响应到页面主要是先构建对应的缓冲流,然后将缓冲流中的数据写入到sockt通道,这样就实现到了页面,具体操作逻辑如下:(自下向上执行)

   下面我将与流相关的几步,进行一下讲述:

process:,AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)

 

if (processor ==
null) {

    processor = createProcessor();

}

 

protected Http11Processor
createProcessor() {

    Http11Processor processor = new Http11Processor(

            proto.getMaxHttpHeaderSize(),
(JIoEndpoint)proto.endpoint,

            proto.getMaxTrailerSize(),
proto.getAllowedTrailerHeadersAsSet(),

            proto.getMaxExtensionSize(),
proto.getMaxSwallowSize());

    proto.configureProcessor(processor);

    // BIO specificconfiguration

    processor.setDisableKeepAlivePercentage(proto.getDisableKeepAlivePercentage());

    register(processor);

    return processor;
}

 

public Http11Processor(int
headerBufferSize,
JIoEndpointendpoint, int
maxTrailerSize,

        Set<String>allowedTrailerHeaders, int
maxExtensionSize, int
maxSwallowSize){

    super(endpoint);

    inputBuffer =
new InternalInputBuffer(request,
headerBufferSize);

    request.setInputBuffer(inputBuffer);

    outputBuffer =
new InternalOutputBuffer(response,
headerBufferSize);

    response.setOutputBuffer(outputBuffer);

    initializeFilters(maxTrailerSize,
allowedTrailerHeaders,
maxExtensionSize,
maxSwallowSize);

}

这里不难看出构建了的outputBuffer这InternalOutputBuffer实例并与response进行关联,所以后面通过response进行一些相关属性操作就可以直接到缓冲流

 

process:,AbstractHttp11Processor(org.apache.coyote.http11)

 

getOutputBuffer().init(socketWrapper, endpoint);

/**

 * 给当前实例 outputBuffer即response封装的对象

 *

 * 给其成员变量NioChannel socket
以及pool进行赋值

 *

 * */
@Override
publicvoid init(SocketWrapper<NioChannel> socketWrapper,

        AbstractEndpoint<NioChannel>endpoint)
throws IOException {

    socket =socketWrapper.getSocket();

    pool =((NioEndpoint)endpoint).getSelectorPool();

}

这一步进行的操作主要是将outputBuffer这个实例关联对应的socket通道,为最后将缓冲流的数据放入到sockt做铺垫

 

public void close()

    throws IOException{

    if (closed) {

        return;

    }

    if (suspended) {

        return;

    }

    //将缓冲去的字符刷新给页面

    if (cb.getLength()>
0) {

        cb.flushBuffer();

    }

      。。。。。。

}

   最终是将cb给刷新到了然后将数据返回到页面,看一下cb是怎么来的,由下不难看出将OutputBuffer给注入其通道

 

public OutputBuffer(int
size) {

    bb =
new ByteChunk(size);

    bb.setLimit(size);

    bb.setByteOutputChannel(this);

    cb =
new CharChunk(size);

    cb.setLimit(size);

    cb.setOptimizedWrite(false);

    cb.setCharOutputChannel(this);

}

   这样做最后怎么获取数据呢?由下面可以看出其一层一层不断的拆解最后还是到InternalOutputBuffer缓冲实例,所以解析的流数据最终还是经过这个进行处理

 

addToBB:,InternalNioOutputBuffer(org.apache.coyote.http11)

那最终它又是怎么到流中去,得看一下addToBB方法,由两步比较和核心,第一步就是将buf即InternalNioOutputBuffer实例中的数据拷贝到niochannel总去,第二步将niochannel通道中的数据写入到socket通道

private synchronized void addToBB(byte[] buf, int
offset, int
length)

        throws IOException{

    if (length ==
0) return;

    //首先尝试先将数据发送出去

    boolean dataLeft = flushBuffer(isBlocking());
    //这里只有在缓冲区里面已经没有数据了才继续发送

    while (!dataLeft&& length >
0) {

        //首先将要发送的数据copy到niochanel的发送buffer里面去

        int thisTime =transfer(buf,offset,length,socket.getBufHandler().getWriteBuffer());
        //计算还剩下多少字节没有写到niochannel的buffer里面,其实这里也就当做将数据转移到了niochannel的buffer就算是写出去了

        length = length -thisTime;

        //这里用于调整偏移量

        offset = offset +thisTime;

        //调用writeToSocket方法将niochannel的buffer的里面的数据通过socket写出去

        int written =writeToSocket(socket.getBufHandler().getWriteBuffer(),

                isBlocking(), true);
       //如果在tomcat的response里面有writelistener的话,可以异步的写

        if (written ==
0) {

            dataLeft = true;

        } else
{

            dataLeft =flushBuffer(isBlocking());

        }

    }

    NioEndpoint.KeyAttachment ka =(NioEndpoint.KeyAttachment)socket.getAttachment();

    if (ka != null)ka.access();//prevent timeouts for just doing client writes

    if (!isBlocking()&& length >
0) {

        //在非阻塞的发送中,如果实在发送不出去,需要保存在额外的buffer里面

        addToBuffers(buf,
offset,
length);

    }

}

 

下面在看一下具体怎么写到通道里面去

private synchronized int writeToSocket(ByteBufferbytebuffer, boolean
block, boolean
flip) throws
IOException{

    if ( flip ) {

        bytebuffer.flip();

        flipped =
true;

    }

    int written =
0;

    NioEndpoint.KeyAttachmentatt = (NioEndpoint.KeyAttachment)socket.getAttachment();

    if ( att == null
) throw new
IOException("Keymust be cancelled");

    long writeTimeout =att.getWriteTimeout();

    Selector selector = null;

    try {

        selector = pool.get();

    } catch
(IOException x ) {

    }

    try {

        written = pool.write(bytebuffer, socket,selector, writeTimeout, block);
        do
{

            if (socket.flush(true,selector,writeTimeout))break;

        }while
( true
);

    } finally
{

        if ( selector!=
null)pool.put(selector);

    }

    if ( block ||bytebuffer.remaining()==0) {

        bytebuffer.clear();

        flipped =
false;

    }

    return written;

}

 

   pool实例,即NioBlockingSelector,可以看出其有阻塞和非组合两种写入方式,但最后都是通过socket.write(buf)写入socket通道就返回到页面,至于为什么写入到socket通道就能响应到页面可以看一下基于NIO的httpserver实现,主要SocketChannelImpl这个类,这里又一个简易的httpserver的实现,参考链接:

http://www.cnblogs.com/a294098789/p/5676566.html

 

public int write(ByteBuffer buf,
NioChannelsocket,
Selector selector,

                 long writeTimeout, boolean
block) throws
IOException{

    if (
SHARED &&block ) {

        return blockingSelector.write(buf,socket,writeTimeout);

    }

    SelectionKey key = null;

    int written = 0;

    boolean timedout = false;

    int keycount = 1;
//assume we canwrite

    long time =System.currentTimeMillis();
//start the timeout timer

    try {

        while ((!timedout) && buf.hasRemaining() ) {

            int cnt =
0;

            if ( keycount > 0
) { //only write ifwe were registered for a write

                cnt = socket.write(buf);//write thedata

                if (cnt == -1)
throw new EOFException();

                written += cnt;

                if (cnt > 0) {

                    time = System.currentTimeMillis();
//reset ourtimeout timer

                    continue; //wesuccessfully wrote, try again without a selector

                }

                if (cnt==0
&&(!block)) break;
//don't block

            }

            if ( selector!=
null){

                //register OP_WRITE to theselector

                if (key==null) key =socket.getIOChannel().register(selector,
SelectionKey.OP_WRITE);

                else key.interestOps(SelectionKey.OP_WRITE);

                if (writeTimeout==0) {

                    timedout =buf.hasRemaining();

                } else if
(writeTimeout<0) {

                    keycount =selector.select();

                } else
{

                    keycount =selector.select(writeTimeout);

                }

            }

            if (writeTimeout>
0 && (selector ==
null || keycount==
0) ) timedout= (System.currentTimeMillis()-time)>=writeTimeout;

        }//while

        if ( timedout )thrownew
SocketTimeoutException();

    } finally
{

        if (key !=
null) {

            key.cancel();

            if (selector != null)selector.selectNow();//removes the key from this selector

        }

    }

    return written;

}

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: