zoukankan      html  css  js  c++  java
  • tomcat 解析(五)-Tomcat的核心组成和启动过程

    声明:源码版本为Tomcat 6.0.35

    前面的文章中介绍了Tomcat的基本配置,每个配置项也基本上对应了Tomcat的组件结构,如果要用一张图来形象展现一下Tomcat组成的话,整个Tomcat的组成可以如下图所示:

    Tomcat在接收到用户请求时,将会通过以上组件的协作来给最终用户产生响应。首先是最外层的Server和Service来提供整个运行环境的基础设施,而Connector通过指定的协议和接口来监听用户的请求,在对请求进行必要的处理和解析后将请求的内容传递给对应的容器,经过容器一层层的处理后,生成最终的响应信息,返回给客户端。

             Tomcat的容器通过实现一系列的接口,来统一处理一些生命周期相关的操作,而Engine、Host、Context等容器通过实现Container接口来完成处理请求时统一的模式,具体表现为该类容器内部均有一个Pipeline结构,实际的业务处理都是通过在Pipeline上添加Valve来实现,这样就充分保证整个架构的高度可扩展性。Tomcat核心组件的类图如下图所示:

    在介绍请求的处理过程时,将会详细介绍各个组件的作用和处理流程。本文将会主要分析Tomcat的启动流程,介绍涉及到什么组件以及初始化的过程,简单期间将会重点分析HTTP协议所对应Connector启动过程。

    Tomcat在启动时的重点功能如下:

    • 初始化类加载器:主要初始化CommonLoader、CatalinaLoader以及SharedLoader;

    • 解析配置文件:使用Digester组件解析Tomcat的server.xml,初始化各个组件(包含各个web应用,解析对应的web.xml进行初始化);

    • 初始化连接器:初始化声明的Connector,以指定的协议打开端口,等待请求。

    不管是通过命令行启动还是通过Eclipse的WST server UI,Tomcat的启动流程是在org.apache.catalina.startup. Bootstrap类的main方法中开始的,在启动时,这个类的核心代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static void main(String args[]) {
            if (daemon == null) {
                daemon = new Bootstrap();//实例化该类的一个实例
                try {
                    daemon.init();//进行初始化
                catch (Throwable t) {
                    ……;
                }
            }
            try {
        ……//此处略去代码若干行
        if (command.equals("start")) {
                    daemon.setAwait(true);
                    daemon.load(args);//执行load,生成组件实例并初始化
                    daemon.start();//启动各个组件
                }
        ……//此处略去代码若干行
        }

    从以上的代码中,可以看到在Tomcat启动的时候,执行了三个关键方法即init、load、和start。后面的两个方法都是通过反射调用org.apache.catalina.startup.Catalina的同名方法完成的,所以后面在介绍时将会直接转到Catalina的同名方法。首先分析一下Bootstrap的init方法,在该方法中将会初始化一些全局的系统属性、初始化类加载器、通过反射得到Catalina实例,在这里我们重点看一下初始化类加载器的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private void initClassLoaders() {
            try {
                commonLoader = createClassLoader("common"null);
                if( commonLoader == null ) {
                    // no config file, default to this loader - we might be in a 'single' env.
                    commonLoader=this.getClass().getClassLoader();
                }
                catalinaLoader = createClassLoader("server", commonLoader);
                sharedLoader = createClassLoader("shared", commonLoader);
            catch (Throwable t) {
                log.error("Class loader creation threw exception", t);
                System.exit(1);
            }
        }

    在以上的代码总,我们可以看到初始化了三个类加载器,这三个类加载器将会有篇博文进行简单的介绍。

    然后我们进入Catalina的load方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public void load() {
    //……
            //初始化Digester组件,定义了解析规则
            Digester digester = createStartDigester();
            //……中间略去代码若干,主要作用为将server.xml文件转换为输入流
            try {
                inputSource.setByteStream(inputStream);
                digester.push(this);
    //通过Digester解析这个文件,在此过程中会初始化各个组件实例及其依赖关系
                digester.parse(inputSource);
                inputStream.close();
            catch (Exception e) {
               
            }
            // 调用Server的initialize方法,初始化各个组件
            if (getServer() instanceof Lifecycle) {
                try {
                    getServer().initialize();
                catch (LifecycleException e) {
                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                        throw new java.lang.Error(e);
                    else   
                        log.error("Catalina.start", e);
                     
                }
            }
     
        }

    在以上的代码中,关键的任务有两项即使用Digester组件按照给定的规则解析server.xml、调用Server的initialize方法。关于Digester组件的使用,后续会有一篇专门的博文进行讲解,而Server的initialize方法中,会发布事件并调用各个Service的initialize方法,从而级联完成各个组件的初始化。每个组件的初始化都是比较有意思的,但是我们限于篇幅先关注Connector的初始化,这可能是最值得关注的。

    Connector的initialize方法,核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void initialize() throws LifecycleException{
         //该适配器会完成请求的真正处理   
    adapter = new CoyoteAdapter(this);
        //对于不同的实现,会有不同的ProtocolHandler实现类,我们来看    //Http11Protocol,它用来处理HTTP请求
            protocolHandler.setAdapter(adapter);
            try {
                protocolHandler.init();
            catch (Exception e) {
                ……
            }
        }

    在Http11Protocol的init方法中,核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void init() throws Exception {
            endpoint.setName(getName());//endpoint为JIoEndpoint的实现类
            endpoint.setHandler(cHandler);
            try {
                endpoint.init();//核心代码就是调用 JIoEndpoint的初始化方法
            catch (Exception ex) {
               ……
            }
        }

    我们看到最终的初始化方法最终都会调到JIoEndpoint的init方法,网络初始化和对请求的最初处理都是通过该类及其内部类完成的,所以后续的内容将会重点关注此类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public void init() throws Exception {
            if (acceptorThreadCount == 0) {//接受请求的线程数
                acceptorThreadCount = 1;
            }
            if (serverSocket == null) {
                try {
                    if (address == null) {
        //基于特定端口创建一个ServerSocket对象,准备接受请求
                        serverSocket = serverSocketFactory.createSocket(port, backlog);
                    else {
                        serverSocket = serverSocketFactory.createSocket(port, backlog, address);
                    }
                catch (BindException orig) {
                 ……
                }
            }
        }

    在上面的代码中,我们可以看到此时初始化了一个ServerSocket对象,用来准备接受请求。

    如果将其比作赛跑,此时已经到了“各就各位”状态,就等最终的那声“发令枪”了,而Catalina的start方法就是“发令枪”啦:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public void start() {
            if (getServer() == null) {
                load();
            }
            if (getServer() == null) {
                log.fatal("Cannot start server. Server instance is not configured.");
                return;
            }
            if (getServer() instanceof Lifecycle) {
                try {
                    ((Lifecycle) getServer()).start();
                catch (LifecycleException e) {
                    log.error("Catalina.start: ", e);
                }
            }
          //……
     }

    此时会调用Server的start方法,这里我们重点还是关注JIoEndpoint的start方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void start()  throws Exception {
            if (!initialized) {
                init();
            }
            if (!running) {
                running = true;
                paused = false;
                if (executor == null) {
        //初始化处理连接的线程,maxThread的默认值为200,这也就是为什么    //说Tomcat只能同时处理200个请求的来历
                    workers = new WorkerStack(maxThreads);
                }
                for (int i = 0; i < acceptorThreadCount; i++) {
        //初始化接受请求的线程
                    Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
                    acceptorThread.setPriority(threadPriority);
                    acceptorThread.setDaemon(daemon);
                    acceptorThread.start();
                }
            }
        }

    从以上的代码,可以看到,如果没有在server.xml中声明Executor的话,将会使用内部的一个容量为200的线程池用来后续的请求处理。并且按照参数acceptorThreadCount的设置,初始化线程来接受请求。而Acceptor是真正的幕后英雄,接受请求并分派给处理过程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    protected class Acceptor implements Runnable {
            public void run() {
                while (running) {
                    // 接受发送过来的请求
        Socket socket = serverSocketFactory.acceptSocket(serverSocket);
                        serverSocketFactory.initSocket(socket);
                        //处理这个请求
                        if (!processSocket(socket)) {
                            //关闭连接
                            try {
                                socket.close();
                            catch (IOException e) {
                                // Ignore
                            }
                        }
                }
            }
        }

    从这里我们可以看到,Acceptor接受Socket请求,并调用processSocket方法来进行请求的处理。至此,Tomcat的组件整装待命,等待请求的到来。关于请求的处理,会在下篇文章中介绍。

  • 相关阅读:
    阿里云服务器配置
    linux 环境变量问题
    docker 常用操作
    docker 安装的nginx 的默认配置
    camera开发相关
    Ubuntu下使用Git
    Java web项目实现多附件上传
    Java web项目实现上传图片及时预览
    SQL Server游标【转】
    SQL Server游标的使用【转】
  • 原文地址:https://www.cnblogs.com/Struts-pring/p/4917008.html
Copyright © 2011-2022 走看看