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模型。

系列文章直达:

初始化与启动:?spm=0.0.0.0.4yGfpo
容器:?spm=0.0.0.0.2uPEZi
连接器:?spm=0.0.0.0.2uPEZi
一个http请求的经历:?spm=0.0.0.0.2uPEZi
重要的设计模式:?spm=0.0.0.0.2uPEZi

时间: 2024-12-31 01:24:23

Tomcat源码分析----Connector初始化与加载的相关文章

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

在本章节中,以下几个问题会被回答: - web容器和servlet容器的区别是什么: - 在springMVC中的web.xml是什么时候加载到tomcat中的: - tomcat是怎么加载我们的web服务的: - tomcat是怎么实现的热部署: 1 Container基本结构 从上文中有讲到,Connector和Container的初始化工作是由Digester来解析conf/server.xml来完成的.而在server.xml中已经告诉我们了Container的基本结构.那么我们先来看看

tomcat源码分析-Connector初始化与启动

   一个应用应用服务器的性能很大程度上取决于网络通信模块的实现,因而Connector模块对于tomcat来说是重中之重.从tomcat5开始,默认的连接器实现为Coyote实现(orag.apache.tomcat:tomcat-coyote:7.0.57),本文基于coyote实现会回答如下两个问题:     一个http请求是怎么被tomcat监听到的,会有那些处理:     ajp协议干什么用的. 一.Connector配置     通过对Container的初始化分析,我们很自然的会

TOMCAT源码分析——生命周期管理(二)

前言 我在<TOMCAT源码分析--生命周期管理(一)>一文中介绍了TOMCAT生命周期类接口设计.JMX.容器以及基于容器的事件与监听等内容,本文将接着介绍Tomcat7.0中容器生命周期的管理. 容器生命周期 每个容器都会有自身的生命周期,其中也涉及状态的迁移,以及伴随的事件生成,本节详细介绍Tomcat中的容器生命周期实现.所有容器的转态转换(如新疆.初始化.启动.停止等)都是由外到内,由上到下进行,即先执行父容器的状态转换及相关操作,然后再执行子容器的转态转换,这个过程是层层迭代执行的

TOMCAT源码分析——生命周期管理(一)

前言 从server.xml文件解析出来的各个对象都是容器,比如:Server.Service.Connector等.这些容器都具有新建.初始化完成.启动.停止.失败.销毁等状态.tomcat的实现提供了对这些容器的生命周期管理,本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. TOMCAT生命周期类接口设计 我们先阅读图1,从中了解Tomcat涉及生命周期管理的主要类. 图1 Tomcat生命周期类接口设计 这里对图1中涉及的主要类作个简单介绍: Lifecycle:定义了容器生命

Tomcat源码分析——server.xml文件的解析

前言 在<Tomcat源码分析--server.xml文件的加载>一文中我们介绍了server.xml的加载,本文基于Tomcat7.0的Java源码,接着对server.xml文件是如何解析的进行分析. 概要 规则 Tomcat将server.xml文件中的所有元素上的属性都抽象为Rule,以Server元素为例,在内存中对应Server实例,Server实例的属性值就来自于Server元素的属性值.通过对规则(Rule)的应用,最终改变Server实例的属性值. Rule是一个抽象类,其中

TOMCAT源码分析——启动服务

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

TOMCAT源码分析——停止服务

前言 在<TOMCAT源码分析--启动服务>一文中我介绍了Tomcat服务的启动过程分析,本文讲解Tomcat服务是如何停止的. 停止过程分析 我们停止Tomcat的命令如下: sh shutdown.sh 所以,将从shell脚本shutdown.sh开始分析Tomcat的停止过程.shutdown.sh的脚本代码见代码清单10. 代码清单10 os400=false case "`uname`" in OS400*) os400=true;; esac # resolv

android 源码中使用System.loadLibrary加载第三方库

问题描述 android 源码中使用System.loadLibrary加载第三方库 我在android源码中编译一个apk,使用了几个第三方库文件,下面是Android.mk文件内容: LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := debug optional eng LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE

Tomcat源码分析----初始化与启动

1.前言 1.1 问题思考 在阅读tomcat源码前,我们一般都会有如下几个疑问: web容器和servlet容器的区别是什么: 在springMVC中的web.xml是什么时候加载到tomcat中的: tomcat是怎么加载我们的web服务的: tomcat是怎么实现的热部署: 一个http请求是怎么被tomcat监听到的,会有哪些处理: 为什么请求可以有需要通过nginx的,也可以不需要nginx的直接请求到tomcat上? -- 如果你想知道答案,那么接下来的文章会告诉你. 1.2 基本姿