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

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;

}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ChaMd5安全团队

Real World CTF国际大赛 部分WP

题目描述里写平台很安全,请不要攻击。 所以尝试抓包,往Cookie的uid进行sqli

1350
来自专栏熊二哥

快速入门系列--MVC--05行为

    Action执行包含内容比较多,主要有同步/异步Action的概念和执行过程,Authorationfilter, ActionFiltor, Resu...

1937
来自专栏Ryan Miao

Spring cache简单使用guava cache

Spring cache简单使用 前言 spring有一套和各种缓存的集成方式。类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcac...

5967
来自专栏FD的专栏

Brainfuck JIT Compiler in Rust

我们都知道,对于解释型的语言实现来说,性能是大家关注的焦点。比如,这位 Tondbal ik Ni 曾经还说过:

1323
来自专栏圣杰的专栏

ASP.NET Core 中断请求了解一下(翻译)

假设有一个耗时的Action,在浏览器发出请求返回响应之前,如果刷新了页面,对于浏览器(客户端)来说前一个请求就会被终止。而对于服务端来说,又是怎样呢?前一个请...

1093
来自专栏小李刀刀的专栏

[译]Laravel 5.0 之命令及处理程序

本文译自 Matt Stauffer 的系列文章. ---- 本文中涉及的新功能都是关于 Commands 的,这些特性在 Laravel 旧版本中已经有了,但...

3436
来自专栏Java编程技术

常用开源框架扩展清单之原理

评价一个框架是否是优秀的,其中必有一点是该框架是否留足了可扩展的接口。我们在实际做项目中很多情况下就是基于某某框架,然后在这个框架留出的扩展接口上进行业务开发,...

1655
来自专栏祝威廉

Kafka Zero-Copy 使用分析

Kafka 我个人感觉是性能优化的典范。而且使用Scala开发,代码写的也很漂亮的。重点我觉得有四个

1262
来自专栏阿杜的世界

实践基于Redis的分布式锁应用场景实现方式基于Redis的实践参考资料

本文来自社区这周的讨论话题—— 技术专题讨论第四期:漫谈分布式锁,也总结了我对分布式锁的认知和使用经验。

1633
来自专栏木木玲

Netty 源码解析 ——— NioEventLoop 详解

6274

扫码关注云+社区