tomcat架构分析 (connector NIO 实现)

出处:http://gearever.iteye.com

上一篇简单记录了缺省配置的connector的内部构造及消息流,同时此connector也是基于BIO的实现。除了BIO外,也可以通过配置快速部署NIO的connector。在server.xml中如下配置;

Xml代码

<Connector port="80" URIEncoding="UTF-8" protocol="org.apache.coyote.http11.Http11NioProtocol"
               connectionTimeout="20000"
               redirectPort="7443" />

整个tomcat是一个比较完善的框架体系,各个组件之间都是基于接口的实现,所以比较方便扩展和替换。像这里的“org.apache.coyote.http11.Http11NioProtocol”和BIO的“org.apache.coyote.http11.Http11Protocol”都是统一的实现org.apache.coyote.ProtocolHandler接口,所以从整体结构上来说,NIO还是与BIO的实现保持大体一致。 
首先来看一下NIO connector的内部结构,箭头方向还是消息流; 
 
还是可以看见connector中三大块

  • Http11NioProtocol
  • Mapper
  • CoyoteAdapter

基本功能与BIO的类似,参见tomcat架构分析(connector BIO实现)。重点看看Http11NioProtocol. 
和JIoEndpoint一样,NioEndpoint是Http11NioProtocol中负责接收处理socket的主要模块。但是在结构上比JIoEndpoint要复杂一些,毕竟是非阻塞的。但是需要注意的是,tomcat的NIO connector并非完全是非阻塞的,有的部分,例如接收socket,从socket中读写数据等,还是阻塞模式实现的,在后面会逐一介绍。 
如图所示,NioEndpoint的主要流程; 

图中Acceptor及Worker分别是以线程池形式存在,Poller是一个单线程。注意,与BIO的实现一样,缺省状态下,在server.xml中没有配置<Executor>,则以Worker线程池运行,如果配置了<Executor>,则以基于java concurrent 系列的java.util.concurrent.ThreadPoolExecutor线程池运行。

Acceptor 
接收socket线程,这里虽然是基于NIO的connector,但是在接收socket方面还是传统的serverSocket.accept()方式,获得SocketChannel对象,然后封装在一个tomcat的实现类org.apache.tomcat.util.net.NioChannel对象中。然后将NioChannel对象封装在一个PollerEvent对象中,并将PollerEvent对象压入events queue里。这里是个典型的生产者-消费者模式,Acceptor与Poller线程之间通过queue通信,Acceptor是events queue的生产者,Poller是events queue的消费者。

Poller 
Poller线程中维护了一个Selector对象,NIO就是基于Selector来完成逻辑的。在connector中并不止一个Selector,在socket的读写数据时,为了控制timeout也有一个Selector,在后面的BlockSelector中介绍。可以先把Poller线程中维护的这个Selector标为主Selector。 
Poller是NIO实现的主要线程。首先作为events queue的消费者,从queue中取出PollerEvent对象,然后将此对象中的channel以OP_READ事件注册到主Selector中,然后主Selector执行select操作,遍历出可以读数据的socket,并从Worker线程池中拿到可用的Worker线程,然后将socket传递给Worker。整个过程是典型的NIO实现。

Worker 
Worker线程拿到Poller传过来的socket后,将socket封装在SocketProcessor对象中。然后从Http11ConnectionHandler中取出Http11NioProcessor对象,从Http11NioProcessor中调用CoyoteAdapter的逻辑,跟BIO实现一样。在Worker线程中,会完成从socket中读取http request,解析成HttpServletRequest对象,分派到相应的servlet并完成逻辑,然后将response通过socket发回client。在从socket中读数据和往socket中写数据的过程,并没有像典型的非阻塞的NIO的那样,注册OP_READ或OP_WRITE事件到主Selector,而是直接通过socket完成读写,这时是阻塞完成的,但是在timeout控制上,使用了NIO的Selector机制,但是这个Selector并不是Poller线程维护的主Selector,而是BlockPoller线程中维护的Selector,称之为辅Selector。

NioSelectorPool 
NioEndpoint对象中维护了一个NioSelecPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程,这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。

Java代码

public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {
        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
        if ( key == null ) throw new IOException("Key no longer registered");
        KeyAttachment att = (KeyAttachment) key.attachment();
        int written = 0;
        boolean timedout = false;
        int keycount = 1; //assume we can write
        long time = System.currentTimeMillis(); //start the timeout timer
        try {
            while ( (!timedout) && buf.hasRemaining()) {
                if (keycount > 0) { //only write if we were registered for a write
                    //直接往socket中写数据
                    int cnt = socket.write(buf); //write the data
                    lastWrite.set(cnt);
                    if (cnt == -1)
                        throw new EOFException();
                    written += cnt;
                    //写数据成功,直接进入下一次循环,继续写
                    if (cnt > 0) {
                        time = System.currentTimeMillis(); //reset our timeout timer
                        continue; //we successfully wrote, try again without a selector
                    }
                }
                //如果写数据返回值cnt等于0,通常是网络不稳定造成的写数据失败
                try {
                    //开始一个倒数计数器
                    if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
                    //将socket注册到辅Selector,这里poller就是BlockSelector线程
                    poller.add(att,SelectionKey.OP_WRITE);
                    //阻塞,直至超时时间唤醒,或者在还没有达到超时时间,在BlockSelector中唤醒
                    att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
                }catch (InterruptedException ignore) {
                    Thread.interrupted();
                }
                if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {
                    keycount = 0;
                }else {
                    //还没超时就唤醒,说明网络状态恢复,继续下一次循环,完成写socket
                    keycount = 1;
                    att.resetWriteLatch();
                }  

                if (writeTimeout > 0 && (keycount == 0))
                    timedout = (System.currentTimeMillis() - time) >= writeTimeout;
            } //while
            if (timedout)
                throw new SocketTimeoutException();
        } finally {
            poller.remove(att,SelectionKey.OP_WRITE);
            if (timedout && key != null) {
                poller.cancelKey(socket, key);
            }
        }
        return written;
    }

也就是说当socket.write()返回0时,说明网络状态不稳定,这时将socket注册OP_WRITE事件到辅Selector,由BlockPoller线程不断轮询这个辅Selector,直到发现这个socket的写状态恢复了,通过那个倒数计数器,通知Worker线程继续写socket动作。看一下BlockSelector线程的逻辑;

Java代码

public void run() {
            while (run) {
                try {
                    ......  

                    Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
                    while (run && iterator != null && iterator.hasNext()) {
                        SelectionKey sk = (SelectionKey) iterator.next();
                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
                        try {
                            attachment.access();
                            iterator.remove(); ;
                            sk.interestOps(sk.interestOps() & (~sk.readyOps()));
                            if ( sk.isReadable() ) {
                                countDown(attachment.getReadLatch());
                            }
                            //发现socket可写状态恢复,将倒数计数器置位,通知Worker线程继续
                            if (sk.isWritable()) {
                                countDown(attachment.getWriteLatch());
                            }
                        }catch (CancelledKeyException ckx) {
                            if (sk!=null) sk.cancel();
                            countDown(attachment.getReadLatch());
                            countDown(attachment.getWriteLatch());
                        }
                    }//while
                }catch ( Throwable t ) {
                    log.error("",t);
                }
            }
            events.clear();
            try {
                selector.selectNow();//cancel all remaining keys
            }catch( Exception ignore ) {
                if (log.isDebugEnabled())log.debug("",ignore);
            }
        }

使用这个辅Selector主要是减少线程间的切换,同时还可减轻主Selector的负担。以上描述了NIO connector工作的主要逻辑,可以看到在设计上还是比较精巧的。NIO connector还有一块就是Comet,有时间再说吧。需要注意的是,上面从Acceptor开始,有很多对象的封装,NioChannel及其KeyAttachment,PollerEvent和SocketProcessor对象,这些不是每次都重新生成一个新的,都是NioEndpoint分别维护了它们的对象池;

Java代码

ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>()
ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>()
ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>()
ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>() 

当需要这些对象时,分别从它们的对象池获取,当用完后返回给相应的对象池,这样可以减少因为创建及GC对象时的性能消耗。

原文地址:https://www.cnblogs.com/nizuimeiabc1/p/8934185.html

时间: 2024-08-01 18:15:06

tomcat架构分析 (connector NIO 实现)的相关文章

tomcat架构分析-索引

tomcat架构分析 (概览) tomcat架构分析 (容器类) tomcat架构分析 (valve机制) tomcat架构分析 (valve源码导读) tomcat架构分析 (Session管理) tomcat架构分析 (JNDI配置) tomcat架构分析 (JNDI体系绑定) tomcat架构分析 (connector BIO 实现) tomcat架构分析 (connector NIO 实现)

(转)tomcat架构分析

        出处:http://gearever.iteye.com tomcat架构分析 (概览) tomcat架构分析 (容器类) tomcat架构分析 (valve机制) tomcat架构分析 (valve源码导读) tomcat架构分析 (Session管理) tomcat架构分析 (JNDI配置) tomcat架构分析 (JNDI体系绑定) tomcat架构分析 (connector BIO 实现) tomcat架构分析 (connector NIO 实现)

tomcat架构分析(概览)

出处:http://gearever.iteye.com Tomcat是目前应用比较多的servlet容器.关于tomcat本身的特点及介绍,网上已经有很多描述了,这里不再赘述.Tomcat除了能够支撑通常的web app外,其本身高度模块化的架构体系,也能带来最大限度的可扩展性.目前tomcat版本已经衍生到tomcat7,但是主流的版本还是tomcat6.此系列架构体系介绍还是以tomcat6为蓝本. Tomcat是有一系列逻辑模块组织而成,这些模块主要包括: 核心架构模块,例如Server

Tomcat 学习进阶历程之Tomcat架构与核心类分析

前面的http及socket两部分内容,主要是为了后面看Tomcat源码而学习的一些网络基础.从这章开始,就开始实际深入到Tomcat的'内在'去看一看. 在分析Tomcat的源码之前,准备先看一下Tomcat的架构与一些核心类的简单分析,并简单介绍一下Tomcat是如何处理一次Http请求的.这部分内容有相当一部分来源于网络,在此,感谢原作者的贡献. Tomcat的总体架构 Tomcat的架构关系可以从Tomcat的配置文件server.xml中看到端倪. 从上图中可以看出Tomcat 的心脏

Tomcat 架构 (一)

在实践过程中,从WebSphere中实现一个EJB的容器以及从WebLogic中实现一个JMS几乎都是不可能的,然而来自Apache基金会的servlet容器Tomcat至少在理论上是可能做到的. 请注意,这里所说的“接口”也包含抽象类.规范的API可能会提供一个实现的模板,其中包括定义了一些抽象的基本类型的操作来供服务提供者去实现. 而服务提供者应提供这些接口和抽象类的具体实现.例如,在Tomcat中HttpSession接口被以org.apache.catalina.session.Stan

Tomcat架构详解(一)

下面谈谈对Tomcat架构的理解 总体架构: 面向组件架构 基于JMX 事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成,如Server.Service.Connector等,并基于JMX管理这些组件,另外实现以上接口的组件也实现了代表生存期的接口Lifecycle,使其组件履行固定的生存期,在其整个生存期的过程中通过事件侦听LifecycleEvent实现扩展.Tomcat的核心类图如下所示: Catalina:与开始/关闭shell脚本

Tomcat 架构概述

Tomcat 是一个 Web 应用服务器,它是对 HTTP 和 Servlet 规范的实现,简单来说它做了这几件事:处理 HTTP 协议.执行 Servlet 和处理网络 I/O.这里以 6.0.53 版本为例(实现了 HTTP/1.1.Servlet2.5),研究其基本结构. 关于源码版本,我使用的是 tomcat6,因为 7 为了重构有太多的抽象,看着实在费劲,6 代码虽有冗余但读起来很直观,并且低版本也不影响理解 Tomcat 的核心流程. 体系结构 从 server.xml 中就能够看出

tomcat源码 Connector

Connector容器主要负责解析socket请求,在tomcat中的源码位于org.apache.catalina.connector和org.apache.coyote包路径下:通过上两节的分析,我们知道了Connector是Service的子容器,而Service又是Server的子容器.在server.xml文件中配置,然后在Catalina类中通过Digester完成实例化.在server.xml中默认配置了两种Connector的实现,分别用来处理Http请求和AJP请求.Conne

Tomcat架构以及理解sever.xml

Tomcat架构图 当用户在地址栏输入访问地址后,首先识别访问协议(假设为http),那么通过针对于http协议传输的Connector连接器,连接到tomcat的服务中,连接后开始检测Engine下的host主机名中有无匹配项,如果有,通过appBase找到目标文件夹,然后开始访问相应的html文件等:如果没匹配到,执行<Engine name="Catalina" defaultHost="localhost">这一默认匹配项进行默认匹配. 理解se