Tomcat源码解读系列(二)——Tomcat的核心组成和启动过程

若是要用一张图来形象展示一下Tomcat组成的话,整个Tomcat的组成能够以下图所示:java


Tomcat在接收到用户请求时,将会经过以上组件的协做来给最终用户产生响应。web

  1. 首先是最外层的Server和Service来提供整个运行环境的基础设施apache

  2. 而Connector经过指定的协议和接口来监听用户的请求网络

  3. 在对请求进行必要的处理和解析后将请求的内容传递给对应的容器架构

  4. 通过容器一层层的处理后,生成最终的响应信息,返回给客户端socket


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

 

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

Tomcat在启动时的重点功能以下:命令行

一、初始化类加载器:主要初始化CommonLoaderCatalinaLoader以及SharedLoader线程

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

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


无论是经过命令行启动仍是经过Eclipse的WST server UI,Tomcat的启动流程是

org.apache.catalina.startup. Bootstrap类的main方法中开始的,在启动时,这个类的核心代码以下所示:

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启动的时候,执行了三个关键方法即initload、和start。后面的两个方法都是经过反射调用org.apache.catalina.startup.Catalina的同名方法完成的,因此后面在介绍时将会直接转到Catalina的同名方法。


首先分析一下Bootstrapinit方法,在该方法中将会初始化一些全局的系统属性、初始化类加载器、经过反射获得Catalina实例,在这里咱们重点看一下初始化类加载器的方法

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);
        }
}

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


而后咱们进入Catalinaload方法:

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的初始化,这多是最值得关注的。


Connectorinitialize方法,核心代码以下:

public void initialize() throws LifecycleException{
     //该适配器会完成请求的真正处理   
    adapter = new CoyoteAdapter(this);
    //对于不一样的实现,会有不一样的ProtocolHandler实现类,咱们来看	
    //Http11Protocol,它用来处理HTTP请求
        protocolHandler.setAdapter(adapter);
        try {
            protocolHandler.init();
        } catch (Exception e) {
            ……
        }
}


Http11Protocolinit方法中,核心代码以下:

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

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

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对象,用来准备接受请求。


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

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);
            }
        }
      //……
 }

此时会调用Serverstart方法,这里咱们重点仍是关注JIoEndpointstart方法

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是真正的幕后英雄,接受请求并分派给处理过程

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的组件整装待命,等待请求的到来。关于请求的处理,会在下篇文章中介绍。

相关文章
相关标签/搜索