莫那·鲁道

惯于闲看花飞花落, 心念天际云卷云舒.

Coder, 欢迎留言 😆.


GitHub

深入理解-Tomcat(八)源码剖析之连接器

这是我们分析tomcat的第八篇文章,这次我们分析连接器,我们早就想分析连接器了,因为各种原因拖了好久。不过也确实复杂。

首先我们之前定义过连接器:

Tomcat都是在容器里面处理问题的, 而容器又到哪里去取得输入信息呢? Connector就是专干这个的。 他会把从socket传递过来的数据, 封装成Request, 传递给容器来处理。 通常我们会用到两种Connector,一种叫http connectoer, 用来传递http需求的。 另一种叫AJP, 在我们整合apache与tomcat工作的时候,apache与tomcat之间就是通过这个协议来互动的。 (说到apache与tomcat的整合工作, 通常我们的目的是为了让apache 获取静态资源, 而让tomcat来解析动态的jsp或者servlet。)

简单来说,连接器就是接收http请求并解析http请求,然后将请求交给servlet容器。

那么在 Tomcat中 ,那个类表示连接器呢? 答案是 org.apache.catalina.connector.Connector,该类继承自 LifecycleMBeanBase, 也就是说,该类的生命周期归属于容器管理。而该类的父容器是谁呢? 答案是 org.apache.catalina.core.StandardService,也就是我们的Service 组件,StandardService是该接口的标准实现。StandardService 聚合了 Connector 数组和一个Container 容器,也就验证了我们之前说的一个Service 组件中包含一个Container和多个连接器。

那么连接器什么时候初始化被放入容器和JMX呢?这是个大问题,也是我们今天的主要问题。

1. Tomcat 解析 server.xml 并创建对象

我们之前扒过启动源码,我们知道,在Catalina 的 load 方法中是初始化容器的方法,所有的容器都是在该方法中初始化的。Connector 也不例外。我们还记得 Tomcat 的conf 目录下的server.xml 文件吗?

<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />

  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  <Service name="Catalina">
    <Connector port="8061" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

可以看到该配置文件中有2个Connector 标签,有就是说默认有2个连接器。一个是HTTP协议,一个AJP协议。

我们的Connector是什么时候创建的呢?就是在解析这个xml文件的时候,那么是怎么解析的呢?我们平时在解析 xml 的时候经常使用dom4j(真的不喜欢xml,最爱json),而tomcat 使用的是 Digester 解析xml,我们来看看 Catalina.load() 关于解析并创建容器的关键代码:

    public void load() {
        // Create and execute our Digester
        Digester digester = createStartDigester();
        digester.parse(inputSource);
    }

首先创建一个 Digester, 其中的关键代码我们看看:

Digester digester = new Digester();

digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
                         new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");

上面的代码的意思是将对应的字符串创建成对应的角色,以便后面和xml对应便解析。

我们再看看它是如何解析的,由于 digester.parse(inputSource) 这个方法调用层次太深,而且该方法只是解析xml,因此楼主把就不把每段代码贴出来了,我们看看IDEA生成的该方法的方法调用栈:这些方法都是在 rt.jar 包中,因此我们不做分析了。主要就是解析xml。

上图是楼主在 Digester.startDocument 的方法中打的断点。该方法作用为开始解析 xml 做准备。

上图是楼主在 Digester.startElement 的方法中打的断点,startDocument 和 startElement 是多次交替执行的,确定他们执行逻辑的是什么方法呢?从堆栈图中我们可以看到:是 com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse()这个方法,代码我就不贴出来了,很长没有意义,该方法在 819行间接调用 startDocument,在841行间接调用startElement。上下执行,并且回执行多次。因为 xml 会解析多次嘛。

我们重点说说 startElement 方法:

public void startElement(String namespaceURI, String localName,
                             String qName, Attributes list) {
 List<Rule> rules = getRules().match(namespaceURI, match);
        matches.push(rules);
        if ((rules != null) && (rules.size() > 0)) {
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire begin() for " + rule);
                    }
                    rule.begin(namespaceURI, name, list);
                } catch (Exception e) {
                    log.error("Begin event threw exception", e);
                    throw createSAXException(e);
                } catch (Error e) {
                    log.error("Begin event threw error", e);
                    throw e;
                }
            }
        } 
}

楼主只贴了关键代码,就是for循环中的逻辑,便利 Rule 集合,Rule 是什么呢?是我们之前 createStartDigester 方法里创建的。而 Rule 是一个接口,tomcat 中有很多不同的实现。然后循环调用他们的 begin 方法。我们看看有哪些实现:

$R_$UQLJE)Z0{F~C0(KVWIG.png

我们从上图中看到了有很多的实现,而我们今天只关注连接器:也就是 ConnectorCreateRule 的 begin 方法:

@Override
    public void begin(String namespace, String name, Attributes attributes)
            throws Exception {
        Service svc = (Service)digester.peek();
        Executor ex = null;
        if ( attributes.getValue("executor")!=null ) {
            ex = svc.getExecutor(attributes.getValue("executor"));
        }
        Connector con = new Connector(attributes.getValue("protocol"));
        if ( ex != null )  _setExecutor(con,ex);
        
        digester.push(con);
    }
    

方法不长,我们看看该方法逻辑,该方法首先从List中取出一个Service,然后会分别创建2个连接器,一个是HTTP, 一个是AJP,也就是我们配置文件中写的。

现在,我们已经剖析了tomcat 是如何解析xml的,并如何创建对象的,接下来,我们就看看创建对象的逻辑。

2. 创建连接器对象

我们来到我们的Connector类的构造方法:

   public Connector() {
        this(null);
    }

    public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            this.protocolHandler = (ProtocolHandler) clazz.newInstance();// 反射创建protocolHandler 默认 http1.1 协议实现 (org.apache.coyote.http11.Http11Protocol)
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        }
    }

我记得阿里规约里说,构造器不要太复杂,复杂的逻辑请放在init里,不知道tomcat这么写算好还是不好呢?嘿嘿。我们还是来看看我们的逻辑吧。

  1. 根据传进来的字符串设置协议处理器类名(setProtocol 方法中调用了setProtocolHandlerClassName 方法)。
  2. 根据刚刚设置好的 protocolHandlerClassName 反射创建 ProtocolHandler 类型的对象。

既然是反射创建,那么我们就要看看完整的类名是什么了,所以需要看看设置 protocolHandlerClassName 方法的细节:

public void setProtocol(String protocol) {

        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11Protocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            }
        }

    }

此方法会直接进入下面的else块,我们知道,该处可能会传 HTTP 或者 AJP ,根据不同的协议创建不同的协议处理器。也就是连接器,我们看到这里的全限定名是 org.apache.coyote.http11.Http11Protocol 或者 org.apache.coyote.ajp.AjpProtocol,这两个类都是在 coyote 包下,也就是连接器模块。

好了,到现在,我们的 Connector 对象就创建完毕了,创建它的过程同时也根据配置文件创建了 protocolHandler, 他俩是依赖关系。

3. Http11Protocol 协议处理器构造过程

创建了 Http11Protocol 对象,我们有必要看看他的构造过程是什么样的。按照tomcat的性格,一般构造器都很复杂,所以,我们找到该类,看看他的类和构造器:

该类的类说明是这样说的:

抽象协议的实现,包括线程等。处理器是单线程的,特定于基于流的协议,不适合像JNI那样的Jk协议。

我们看看构造方法

    public Http11Protocol() {
        endpoint = new JIoEndpoint();
        cHandler = new Http11ConnectionHandler(this);
        ((JIoEndpoint) endpoint).setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }

我就说嘛,肯定和复杂。复杂也要看啊。

  1. 创建以了一个 JIoEndpoint 对象。
  2. 创建了一个 Http11ConnectionHandler 对象,参数是 Http11Protocol;
  3. 设置处理器为 Http11ConnectionHandler 对象。
  4. 设置一些属性,比如超时,优化tcp性能。

那么我们来看看 JIoEndpoint 这个类,这个类是什么玩意,如果大家平时调试tomcat比较多的话,肯定会熟悉这个类,楼主今天就遇到了,请看:

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)

异常信息,我们看倒数第四行,就是 JIoEndpoint 的内部类 SocketProcessor 的 run 方法报错了,我们今天就亲密接触一下这个类,顺便扒了它的衣服:

赤裸裸的在我们面前。所有错误的根源都在该方法中。

不扯了,我们继续看 JIoEndpoint 的构造器,该构造器很简单,就是设置最大连接数。默认是0,我们看代码:

上图中什么看到该方法将 maxConnections 设置为0,本来是10000,然后进入else if(maxCon > 0) 的逻辑。这里也就完成了 JIoEndpoint 对象的创建过程。

我们回到 Http11Protocol 的构造方法中,执行完了 JIoEndpoint 的创建过程,下面就执行 Http11ConnectionHandler 的构造。参数是Http11Protocol自己,Http11ConnectionHandler 是 Http11Protocol 的静态内部类,该类中有一个属性就是Http11Protocol,一个简单的创建过程,然后设置 Http11Protocol 的 Handler 属性为 Http11ConnectionHandler。可以感觉的到,Http11Protocol, JIoEndpoint , Http11ConnectionHandler 这三个类是互相依赖关系。

至此,完成了 Http11Protocol 对象的创建。同时也完成了 Connector 对象的创建。 创建完对象干嘛呢。。。。不要想歪了,不是啪啪啪,而是初始化。

4. Connector 连接器的初始化 init 方法

我们知道 Connector 的父容器是 Service ,Service 执行 initInternal 方法初始化的时候会同时初始化子容器,也就是 Connector,在一个 for 循环重启动。

该段代码抽取自 StandardService.initInternal 方法,也就是Service 组件。通过debug我们知道了该连接器数组中只有2个连接器,就是我们的HTTP和AJP,刚刚创建的。并调用他们的 init 方法。我们看看该方法,该方法同所有容器一样,执行了LifecycleBase 的模板方法,重点在子类重写的抽象方法 initInternal 中。

这既是 Connector 的 initInternal 方法实现,该方法有几个步骤:

  1. 调用父类的 initInternal 方法,将自己注册到JMX中。
  2. 创建一个 CoyoteAdapter 对象,参数是自己。
  3. 设置 Http11Protocol 的适配器为刚刚创建的 CoyoteAdapter 适配器。
  4. 设置解析请求的请求方法类型,默认是 POST。
  5. 初始化 Http11Protocol(不要小看这个类,Connector就是一个虚的,真正做事的就是这个类和 JIoEndpoint);
  6. 初始化 mapperListener;

我们重点关注 CoyoteAdapter 和 Http11Protocol 的初始化,CoyoteAdapter 是连接器的一种适配,构造参数是 Connector ,很明显,他是要适配 Connector,这里的设计模式就是适配器模式了,所以,写设计模式的时候,一定要在类名上加上设计模式的名字。方便后来人读代码。接下就是设置 Http11Protocol 的适配器为 刚刚构造的 CoyoteAdapter ,也就是说,tomcat 的设计者为了解耦或者什么将 Http11Protocol 和 Connector 中间插入了一个适配器。最后来到我们的 Http11Protocol 的初始化。

这个 Http11Protocol 的初始化很重要。Http11Protocol 不属于 Lifecycle 管理,他的 init 方法在他的抽象父类 org.apache.coyote.AbstractProtocol 中就已经写好了,我们来看看该方法的实现(很重要):

上图就是 AbstractProtocol 的 init 方法,我们看看红框中的逻辑。

  1. 将 endpoint 注册到JMX中。
  2. 将 Http11ConnectionHandler(Http11Protocol 的 内部类)注册到JMX中。
  3. 设置 endpoint 的名字,Http 连接器是 http-bio-8080;
  4. endpoint 初始化。

设置JMX的逻辑我们就不讲了,之前讲生命周期的时候讲过了,设置名字也没生命好讲的。最后讲最重要的 endpoint 的初始化。我们来看看他的 init 方法。该方法是 JIoEndpoint 抽象父类 AbstractEndpoint 的模板方法。该类被3个类继承:AprEndpoint, JIoEndpoint, NioEndpoint,我们今天只关心JIoEndpoint。我们还是先看看 AbstractEndpoint 的 init 方法吧:

其中 bind()是抽象方法,然后设置状态为绑定已经初始化。我们看看 JIoEndpoint 的 bind 方法。有兴趣也可以看看其他 Endpoint 的 bind 方法,比如NIO。我们看看JIo的:

这个方法很重要,我们仔细看看逻辑:

  1. 设置最大线程数,默认是200;
  2. 创建一个默认的 serverSocketFactory 工厂(就是一个封装了ServerSocket 的类);
  3. 使用刚刚工厂创建一个 serverSocket。因此,JIoEndpoint 也就有了 serverSocket。

至此,我们完成了 Connector, Http11Protocol,JIoEndpoint 的初始化。

接下来就是启动了

5. 连接器启动

如我们所知,Connector 启动肯定在 startInternal 方法中,因此我们直接看此方法。

该方法步骤如下:

  1. 设置启动中状态。状态更新会触发事件监听机制。
  2. 启动 org.apache.coyote.http11.Http11Protocol 的 srart 方法。
  3. 启动 org.apache.catalina.connector.MapperListener 的 start 方法。

我们感兴趣的是 org.apache.coyote.http11.Http11Protocol 的 srart 方法。该方法由其抽象父类 AbstractProtocol.start 执行,我们看看该方法:

该方法主要逻辑是启动 endpoint 的 start 方法。说明干事的还是 endpoint 啊 ,我们看看该方法实现,该方法调用了抽象父类的模板方法 AbstractEndpoint.start:

其主要逻辑是调用子类重写的 startInternal 方法,我们来看 JIoEndpoint 的实现:

该方法可以说是 Tomcat 中 真正做事情的方法,绝对不是摸鱼员工。说说他的逻辑:

  1. 创建一个线程阻塞队列,和一个线程池。
  2. 初始化最大连接数,默认200.
  3. 调用抽象父类 AbstractEndpoint 的 startAcceptorThreads 方法,默认创建一个守护线程。他的任务是等待客户端请求,并将请求(socket 交给线程池)。AbstractEndpoint 中有一个 Acceptor 数组,作用接收新的连接和传递请求。
  4. 创建一个管理超时socket 的线程。

让我们看看他的详细实现:

6. JIoEndpoint startInternal(Tomcat socket 管理) 方法的详细实现

先看第一步:创建一个线程阻塞队列,和一个线程池。我们进入该方法:

该方法步骤:

  1. 创建一个 “任务队列”,实际上是一个继承了 LinkedBlockingQueue 的类。该队列最大长度为 int 最大值 0x7fffffff。
  2. 创建一个线程工厂,TaskThreadFactory 是一个继承 ThreadFactory 的类,默认创建最小线程 10, 最大线程200, 名字为 “http-bio-8080-exec-” 拼接线程池编号,优先级为5。
  3. 使用上面的线程工厂创建一个线程池,预先创建10个线程,最大线程200,线程空闲实际60秒.
  4. 将线程池设置为队列的属性,方便后期判断线程池状态而做一些操作。

再看第二步:初始化最大连接数,默认200.

该方法很简单,就是设置最大连接数为200;

第三步:用抽象父类 AbstractEndpoint 的 startAcceptorThreads 方法,默认创建一个守护线程。他的任务是等待客户端请求,并将请求(socket 交给线程池)。AbstractEndpoint 中有一个 Acceptor 数组,作用接收新的连接和传递请求。我们看看该方法:

步骤:

  1. 获取可接收的连接数,并创建一个连接线程数组。
  2. 循环该数组,设置优先级为5,设置为守护线程。 3.启动该线程。

该方法也不是很复杂,获取的这个连接数,在完美初始化的时候,调用bind 方法的时候设置的,请看:

设置为1.

复杂的是 Acceptor 中的逻辑,Acceptor 是一个抽象静态内部类,实现了 Runnable 接口,JIoEndpoint 类中也继承了该类,其中 run 方法如下(高能预警,方法很长)。

        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {

                // Loop if endpoint is paused
                while (paused && running) {
                    state = AcceptorState.PAUSED;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                if (!running) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    Socket socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSocketFactory.acceptSocket(serverSocket);
                    } catch (IOException ioe) {
                        countDownConnection();
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Configure the socket
                    if (running && !paused && setSocketOptions(socket)) {
                        // Hand this socket off to an appropriate processor
                        if (!processSocket(socket)) {
                            countDownConnection();
                            // Close socket right away
                            closeSocket(socket);
                        }
                    } else {
                        countDownConnection();
                        // Close socket right away
                        closeSocket(socket);
                    }
                } catch (IOException x) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), x);
                    }
                } catch (NullPointerException npe) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), npe);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }
            state = AcceptorState.ENDED;
        }

其实逻辑也还好,不是那么复杂,我们化整为零,一个一个分析,首先判断状态,进入循环,然后设置一些状态,最后进入一个 try 块。

  1. 执行 countUpOrAwaitConnection 方法,该方法注释说:如果已经达到最大连接,就等待。
  2. 阻塞获取socket。
  3. setSocketOptions(socket) 设置 tcp 一些属性优化性能,比如缓冲字符大小,超时等。
  4. 执行 processSocket(socket) 方法,将请求包装一下交给线程池执行。

我们看看第一个方法 countUpOrAwaitConnection:

主要是 latch.countUpOrAwait() 这个方法,我们看看该方法内部实现:

这个 Sync 类 变量是 继承了java.util.concurrent.locks.AbstractQueuedSynchronizer 抽象类(该类是JDK 1.8 新增的),说实话,楼主不熟悉这个类。再一个今天的主题也不是并发,因此放过这个类,给大家一个链接 深度解析Java 8:AbstractQueuedSynchronizer的实现分析(下); 我们暂时知道这个方法作用是什么就行了,就像注释说的:如果已经达到最大连接,就等待。我们继续我们的分析。

我们跳过设置 tcp 优化,重点查看 processSocket 方法,这个方法是 JIoEndpoint 的,我们看看该方法实现:

该方法逻辑是:

  1. 封装一个 SocketWrapper。
  2. 设置长连接时间为100, 3.然后封装成 SocketProcessor(还记得这个类吗,就是我们刚开始异常信息里出现的类,原来是在这里报错的,哈哈) 交给线程池执行。

到这里,我们必须停下来,因为如果继续追踪 SocketProcessor 这个类,这篇文章就停不下来了,楼主想留在下一篇文章慢慢咀嚼。慢慢品味。

第四步:好了,回到我们的 JIoEndpoint.startInternal 方法,我们已经解析完了 startAcceptorThreads 方法,那么我们继续向下走,看到一个 timeoutThread 线程。创建一个管理超时socket 的线程。设置为了守护线程,名字叫 “http-bio-8080-AsyncTimeout”,优先级为5.

我们看看该程序的实现,还好,代码不多:

我们看看主要逻辑:

  1. 获取当前时间;
  2. waitingRequests 的类型是 ConcurrentLinkedQueue<SocketWrapper>,一个并发安全的阻塞对垒,里面有包装过的 SocketWrapper。
  3. 判断如果该队列中有,则取出,判断如果该socket 设定的超时时间大于0(默认-1),且当前时间大于访问时间,则交给线程池处理。

那么什么时候会往该 waitingRequests 里添加呢?我们看过之前的 SocketProcessor. run 方法, 如果 SocketState 的状态是LONG,就设置该 socket 的访问时间为当前时间,并添加进超时队列。而这个超时的判断也非常的复杂,想想也对,任何一个连接都不能随意丢弃。所以需要更严谨的对待,万一是个支付请求呢?

好了,JIoEndpoint 的 startInternal 方法已经执行完毕,总结一下该方法:创建线程池,初始化最大连接数,启动接收请求线程,设置超时线程。可以说tomcat 考虑的很周到。很完美。不过还有更完美的NIO还没有体验。

7. 总结

今天的文章真是超长啊,谁叫连接器是tomcat中最重要的组件呢?其实还没有讲完呢;我们讲了连接器的如何根据server.xml 创建对象,如何初始化connector 和 endpoint ,如何启动connector,如何启动 endpoint中各个线程。楼主能力有限,暂时先讲这么多,还有很多的东西我们都还没讲,没事,留着下次讲。

天色已晚,楼主该回家了。各位再见!!!!

good luck !!!!