Tomcat之NIO 啟動與應用分析


概述

    從入門Web開始一直在使用Tomcat,隨着對網絡相關的知識的進一步了解,覺得越有必有去閱讀一下常用的開源服務器的整個工作流程,以及使用場景,對比幾款服務器的優劣勢、最終根據合適的業務場景進行優化。於是有了這一篇啟動相關的源碼分析,使用到的 Tomcat版本為  9.0.6 ,技術有限,難免出現錯誤,歡迎指出,也期待各路大神指點。

 

  首先啟動Tomcat方式這里采取編程的方式,maven引入坐標

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.6</version>
        </dependency>

 

1.啟動方法

        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        tomcat.setConnector(connector);
        tomcat.start();
        tomcat.getServer().await();
  1. Connector 的構造方法默認會使用Http11NioProtocol協議(NIO)來對客戶端連接進行處理,通過反射獲得其對象,賦值給 protocolHandler
  2. tomcat.setConnector()會初始化一個 StandardServer和StandardService,並且將StandardServicet添加到StandardServer里,Connector添加 StandardService里,因此這里三者之間的關系就是
  3. Tomcat.start() 則利用初始化的StandardServer,調用其start()方法
  4. Http11Nioprotocol 協議是用來處理Nio的協議,在它的構造方法里,實例化了NioEnpoint 交給 AbstractProtocol 保管,然而真正實現Nio建立網絡連接則是通過該類完成。后面細說是如何到NioEnponit的

  Connector默認使用Nio協議

public Connector() {
        this("org.apache.coyote.http11.Http11NioProtocol");
    }


    public Connector(String protocol) {
        boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
                AprLifecycleListener.getUseAprConnector();

        if ("HTTP/1.1".equals(protocol) || protocol == null) {
            if (aprConnector) {
                protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
            } else {
                protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
            }
        } else if ("AJP/1.3".equals(protocol)) {
            if (aprConnector) {
                protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
            } else {
                protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
            }
        } else {
            protocolHandlerClassName = protocol;
        }

        // Instantiate protocol handler
        ProtocolHandler p = null;
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            //反射獲得對象
            p = (ProtocolHandler) clazz.getConstructor().newInstance();
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        } finally {
        }
            this.protocolHandler = p;
        }


 

 

 

2.LifecycleBase.start()  初始化與啟動

   

@Override
    public final synchronized void start() throws LifecycleException {
        //state默認是 LifecycleState.NEW,首次執行時,先通過init()方法實現初始化
        if (state.equals(LifecycleState.NEW)) {
            init();
        }
        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            //初始化之后,開始啟動tomcat服務器
            startInternal();
     ........... }

  

 

   

 

  1. LifecycleBase 是 StandardServer、Connector、StandardService的父類,當StandardServer的 start() 方法被調用時,將會調用父類LifecycleBase的 start() 方法,同樣的,對Connector、StandardService來說,調用 start() 方法也會調用父類的LifecycleBase的start()方法。

  2. LifecycleBase 中持有 用 volatile 關鍵字 修飾的state 字段,該字段用來標識 Tomcat 啟動時所處的狀態,默認是 LifecycleState.NEW,因此進入init() 方法 
  3. 每個LifecycleState枚舉類一 一對應着Tomcat 服務器的狀態,每個枚舉類狀態又有對應的Tomcat啟動的事件,可以通過實現  LifecycleListener 接口進行自定義。在Tomcat啟動時會被放進 lifecycleListeners 中
public enum LifecycleState {
    NEW(false, null),
    INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
    INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
    STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
    STARTING(true, Lifecycle.START_EVENT),
    STARTED(true, Lifecycle.AFTER_START_EVENT),
    STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
    STOPPING(false, Lifecycle.STOP_EVENT),
    STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
    DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
    DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
    FAILED(false, null);

    private final boolean available;
    private final String lifecycleEvent;

 

 3.LifecycleBase.init()  初始化StandardServer 

 

public final synchronized void init() throws LifecycleException {
        if (!this.state.equals(LifecycleState.NEW)) {
            this.invalidTransition("before_init");
        }

        try {
            //從lifecycleListeners 獲取 before_init事件進行調用
            this.setStateInternal(LifecycleState.INITIALIZING, (Object)null, false);
            this.initInternal();
            //對應 after_init 事件
            this.setStateInternal(LifecycleState.INITIALIZED, (Object)null, false);
        } catch (Throwable var2) {
            this.handleSubClassException(var2, "lifecycleBase.initFail", this.toString());
        }
    }
  1. init() 方法是通過調用子類實現的initInternal() 方法來進行抽象的擴展,相當於一個沒有具體實現的抽象函數,交給子類去初始化處理(這里是StandardServer 的調用過程)
  2. 在初始化方法調用的前后,使用了setStateInternal() 方法進行捕捉,這里便是對 實現了 LifecycleListener 接口的監聽器做了實現,在其他地方,如:start、stop 都有進行 listener 的調用

4.StandardServer.initInternal() 初始化 server 

  

   @Override
    protected void initInternal() throws LifecycleException {
        super.initInternal();
        // ...... 暫時不關心中間代碼
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            //這里調用StandardService的init()方法
            services[i].init();
        }
    }
  1. 步驟3 處理邏輯大致相同,init()方法里 是通過調用子類實現的 initInternal() 方法來初始化,同樣的捕捉初始化的前后事件  before_init、after_init 

5. StandardService.initInternal()  初始化service

@Override
    protected void initInternal() throws LifecycleException {
        super.initInternal();
        //省略若干......
        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                //此處調用connector進行初始化
                connector.init();
            }
        }
    }
  1.  此處同理,調用 connector.init(),通過父類調用子類實現的 鈎子函數 initInternal() 來初始化Connector

6.Connector.initInternal() 初始化connector

  

@Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();
        //省略若干..............
        try {
            //初始化 Http11NioProtocol協議
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }
    }
  1. protocolHandler是我們在實例化 Connector 時,使用 默認的Http11NioProtocol協議。
  2. 其init()方法,則在父類 AbstractProtocol 中實現

 

6.AbstractProtocol.init()  初始化協議

  

    @Override
    public void init() throws Exception {
        if (getLog().isInfoEnabled()) {
            getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
        }

        //省略若干行...

        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length()-1));
        endpoint.setDomain(domain);
        //初始化NioEndpoint
        endpoint.init();
    }
  1. NioEndpoint 的init() 方法,在其父類AbstractEndpoint中實現,用來把公共的抽象到父類中實現(如注冊JMX),
  2. 然后再提供給子類bind() 方法,通過實現該方法來完成一次函數的回調
public final void init() throws Exception {
        if (bindOnInit) {
            //交給子類實現
            bind();
            bindState = BindState.BOUND_ON_INIT;
        }
        if (this.domain != null) {
            // Register endpoint (as ThreadPool - historical name)
            oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
            Registry.getRegistry(null, null).registerComponent(this, oname, null);

            for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
                registerJmx(sslHostConfig);
            }
        }
    }

 

7. NioEndpoint.bind() 

@Override
    public void bind() throws Exception {
        //初始化ServerSocker
        initServerSocket();

        // Initialize thread count defaults for acceptor, poller
        //如果acceptor線程個數小於0,則默認為1
        if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
        //如果poller線程數小於0,則默認為1
        if (pollerThreadCount <= 0) {
            //minimum one poller thread
            pollerThreadCount = 1;
        }

        //獲取一個柵欄,通過AQS實現的一個共享鎖
        setStopLatch(new CountDownLatch(pollerThreadCount));

        // Initialize SSL if needed
        //初始化ssl配置
        initialiseSsl();
   
        selectorPool.open();
    }

 

 

  1. 第一步初始化Socket線程

 

 

  1. 調用initServerSocket()方法,初始化Socket線程,可見這里線程為阻塞,目的能解釋得通的也就是為了操作方便,但后續建立連接后的socket,會設為非阻塞的
  2. 初始化accptor與poller線程個數(分別為1 , 2) 
  3. 獲取柵欄 CountDownLatch,用於在在高並發的場景時,允許並發請求個數最大值,默認 10000
  4. 這里需要提出一個Acceptor 和 Poller 的概念。Acceptor主要的職責就是監聽是否有客戶端套接字連接,並接收套接字,連接之后,把Socket 注冊到Poller輪詢器中的Selector中,如圖

 

總結:

  到此為止初始化的過程已經結束,大致過程也就是通過把公共的東西交給高度抽象的父類處理,然后父類去調用提供給子類實現的回調函數,此時利用組合模式,在每次初始化對象完畢之后,再將其持有的對象調用其共同的父類的初始化方法,這樣便十分簡潔。后續將繼續回到StandardServer的父類調用init()方法完畢時繼續往下走,執行 startInternal()方法,但大致也與init 時相同,便將核心的加載方法拿出來。

 

8. NioEndpoint. startInternal()  啟動Nio相關線程池

  

@Override
    public void startInternal() throws Exception {
        if (!running) {
            running = true;
            paused = false;
            //處理連接時緩存用
            processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getProcessorCache());
            eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getEventCache());
            nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getBufferPool());

            //創建工作的線程池,默認最小10,最大200
            // Create worker collection
            if ( getExecutor() == null ) {
                createExecutor();
            }

            initializeConnectionLatch();

            //創建 2個 Poller 線程
            // Start poller threads
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
            }
            //啟動1個Acceptor線程
            startAcceptorThreads();
        }
    }
  1. 到此為止,Tomcat便已經啟動完成。這里一共啟動了 1個Acceptor線程,2個poller線程,10個work線程(啟動時,不考慮有連接數並發超過10個任務)

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM