Tomcat源码分析----Connector初始化与加载

一个应用服务器的性能很大程度上取决于网络通信模块的实现,因此Connector对于tomcat而言是重中之重。在本章节中以下两个问题会被回答:

  • 一个http请求是怎么被tomcat监听到的,会有哪些处理;
  • 为什么请求可以有需要通过nginx的,也可以不需要nginx的直接请求到tomcat上?

1 Connector配置

通过对Container的初始化分析,我们很自然的会回过头看conf/server.xml中的connector配置。在xml中配置了2个connector。

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

为什么会有多个Connector呢?我们部署服务器的时候,通常会有2种方式:

  • 1 直接部署tomcat,在浏览器中请求http与tomcat直连
  • 2 部署一个nginx作反向代理,tomcat与nginx直连

这就是上面两种配置,通过协议protocol来区分。所以多个connector的好处是通过不同的协议,是tomcat服务器能够随着http的应用场景,服务器架构的升级而兼容起来。

好的,现在配置了2个Connector,那么继续思考一下,Connector是通信过程,如果是你你会怎么设计?显然需要做3件事:

  • (1)监听端口,创建服务端与客户端的链接;
  • (2)获取到客户端请求的socket数据,并对Socket数据进行解析和包装成Http请求数据格式;
  • (3)将包装后的数据交给Container处理。

通过源码来分析,Connector有两个属性:protocolHandler(协议)和adapter(适配器),其中protocolHandler完成的是步骤(1)(2),adapter完成的是步骤(3)。

2 初始化工作

2.1 Connector构造函数

在Connector的构造方法中,通过反射生成protocolHandler.

public Connector(String protocol) {
    setProtocol(protocol);
    // Instantiate protocol handler
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        this.protocolHandler = (ProtocolHandler) clazz.newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    }
}

协议的设置在conf/server.xml中配置,由setProtocol来赋值,Tomcat提供了6种协议:
三种不带Ajp的协议,客户端与Tomcat服务器直接连接。
Http11Protocol---------------默认协议,阻塞IO方式的支持http1.1协议
Http11NioProtocol----------非阻塞IO方式的支持http1.1协议
Http11AprProtocol----------使用ARP技术的支持http1.1协议(ARP:Apache portable runtime)
三种带Ajp的协议为定向包协议,即WEB服务器通过 TCP连接和SERVLET容器连接,例如tomcat和Apache、Nginx等前端服务器连接
AjpProtocol--------------------阻塞IO方式的支持Ajp协议
AjpNioProtocol---------------非阻塞IO方式的支持Ajp协议
AjpAprProtocol---------------使用ARP技术的支持Ajp协议
为了便于分析,之对Http11Protocol进行分析,其他的大同小异。在Http11Protocol的构造方法中,对成员变量endpoint和cHandler进行初始化,这两个很重要,后续会继续讲到。
继续到Connector代码中,由前面提到的tomcat启动过程知道,会调用Connector两个方法init和start。而端口的绑定和监听则分别在这两个方法中完成

2.2 Connector的init方法

调用的是initInternal,做了三件事:

  • 1 适配器初始化成CoyoteAdapter;
  • 2 调用Http11Protocol的init方法;
  • 3 调用mapperListener的init方法。
protected void initInternal() throws LifecycleException {

    super.initInternal();
    //TODO:注意,是在在这里初始化的Adapter,将是配置赋值给协议处理类
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    ……
    try {
        //TODO:在这里初始化http11protocol
        protocolHandler.init();
    } catch (Exception e) {
        ……
    }
    ……
    mapperListener.init();
}

步骤2中Http11Protocol的init方法,最终会调用到其父类AbstractProtocol的init方法,在这个方法里面对endpoint(Http11Protocol使用的是JIoEndPoint)进行了初始化。

public void init() throws Exception {
    ……
    try {
        //endpoint进行了初始化。
        endpoint.init();
    } catch (Exception ex) {
        ……
    }
}

endpoint.init()在AbstractEndpoint中,完成了对需要监听的端口的绑定。

public final void init() throws Exception {
    testServerCipherSuitesOrderSupport();
    if (bindOnInit) {
        bind();//绑定服务端需要监听的端口
        bindState = BindState.BOUND_ON_INIT;
    }
}

在JIoEndpoint的bind()中完成了对端口的绑定。

2.3 Connector的start方法

Connector的启动会调用start方法,在startInternal方法中,

protected void startInternal() throws LifecycleException {
    ……
    setState(LifecycleState.STARTING);//发送STARTING事件
    try {
        protocolHandler.start();//启动端口监听
    }
    ……
    mapperListener.start();//很重要,后面会提到
}

其中,protocolHandler.start();即调用了Http11Protocol的start方法。最终调用了调用了JIoEndpoint的startInternal方法,初始化了连接请求处理的线程池,监听端口的线程(默认200个)

public void startInternal() throws Exception {
    if (!running) {
        ……
        if (getExecutor() == null) {
            createExecutor();//初始化请求处理的线程池
        }
        initializeConnectionLatch();//设置链接线程阈值
        startAcceptorThreads();//初始化监听接收客户端请求的线程
        ……
    }
}

OK,到了现在Connector的启动已经透明化了,Connector的初始化工作其实是根据server.xml的配置,创建了服务端Socket来监听客户端的请求。并初始化了一个线程池来处理接收到的请求。
通过上面的分析,我们可以看到JIoEndpoint是一个阻塞式的IO模型;而若使用Http11NioProtocol协议,则调用的是NioEndpoint,是一个多路复用IO模型。

系列文章直达:

初始化与启动:https://yq.aliyun.com/articles/20169?spm=0.0.0.0.4yGfpo
容器:https://yq.aliyun.com/articles/20172?spm=0.0.0.0.2uPEZi
连接器:https://yq.aliyun.com/articles/20175?spm=0.0.0.0.2uPEZi
一个http请求的经历:https://yq.aliyun.com/articles/20177?spm=0.0.0.0.2uPEZi
重要的设计模式:https://yq.aliyun.com/articles/20179?spm=0.0.0.0.2uPEZi

上一篇:Splay


下一篇:Tomcat源码分析----初始化与启动