1. 程式人生 > >tomcat從啟動到接軌Servlet二三事

tomcat從啟動到接軌Servlet二三事

緣由

也是因為之前自己的不謹慎,在寫Java程式設計方法論-Reactor與Webflux的時候,因覺得tomcat關於connector部分已經有不錯的博文了,草草參考了下,並沒有對原始碼進行深入分析,導致自己在錄製分享視訊的時候,發現自己文章內容展現的和原始碼並不一致,又通過搜尋引擎搜尋了一些中文部落格的文章,並不盡如人意,索性,自己的就通過最新的原始碼來重新梳理一下關於tomcat connector部分內容,也是給自己一個警醒,凡事務必仔細仔細再仔細! 參考原始碼地址: github.com/apache/tomc…

關於Java程式設計方法論-Reactor與Webflux的視訊分享,已經完成了Rxjava 與 Reactor,b站地址如下:

Rxjava原始碼解讀與分享:www.bilibili.com/video/av345…

Reactor原始碼解讀與分享:www.bilibili.com/video/av353…

Tomcat的啟動過程詳解

啟動與結束Tomcat基本操作

在Linux系統下,啟動和關閉Tomcat使用命令操作。

進入Tomcat下的bin目錄:

cd /java/tomcat/bin
複製程式碼

啟動Tomcat命令:

./startup.sh
複製程式碼

停止Tomcat服務命令:

./shutdown.sh
複製程式碼

執行tomcat 的./shutdown.sh後,雖然tomcat服務不能正常訪問了,但是ps -ef | grep tomcat

後,發現tomcat對應的java程序未隨web容器關閉而銷燬,進而存在殭屍java程序。網上看了下導致殭屍程序的原因可能是有非守護執行緒(即User Thread)存在,jvm不會退出(當JVM中所有的執行緒都是守護執行緒的時候,JVM就可以退出了;如果還有一個或以上的非守護執行緒則JVM不會退出)。通過一下命令檢視Tomcat程序是否結束:

ps -ef|grep tomcat
複製程式碼

如果存在使用者執行緒,給kill掉就好了即使用kill -9 pid

啟動過程Bootstrap詳解

我們接著從startup.sh這個shell指令碼中可以發現,其最終呼叫了catalina.sh start

,於是,我們找到catalina.sh裡,在elif [ "$1" = "start" ] ;處,我們往下走,可以發現,其呼叫了org.apache.catalina.startup.Bootstrap.java這個類下的start()方法:

/**
* org.apache.catalina.startup.Bootstrap
* Start the Catalina daemon.
* @throws Exception Fatal start error
*/
public void start()
    throws Exception {
    if( catalinaDaemon==null ) init();

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);

}
複製程式碼

這裡,在伺服器第一次啟動的時候,會呼叫其init(),其主要用於建立org.apache.catalina.startup.Catalina.java的類例項:

/**
* org.apache.catalina.startup.Bootstrap
* Initialize daemon.
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;

}

複製程式碼

啟動過程Catalina詳解

Catalina中start解讀

接著,在Bootstrap的start()方法中會呼叫Catalina例項的start方法:

/**
* org.apache.catalina.startup.Catalina
* Start a new server instance.
*/
public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal(sm.getString("catalina.noServer"));
        return;
    }

    long t1 = System.nanoTime();

    // Start the new server
    try {
        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
    }

    // Register shutdown hook
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

    if (await) {
        await();
        stop();
    }
}
複製程式碼

在這裡面,我們主要關心load()getServer().start(),對於後者,在它的前後我們看到有啟動時間的計算,這也是平時我們在啟動tomcat過程中所看到的日誌列印輸出所在,後面的我這裡就不提了。

Catalina中load()解讀

首先我們來看load(),這裡,其會通過createStartDigester()建立並配置我們將用來啟動的Digester,然後獲取我們所配置的ServerXml檔案,依次對裡面屬性進行配置,最後呼叫getServer().init():

/**
* org.apache.catalina.startup.Catalina
* Start a new server instance.
*/
public void load() {

    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();

    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
    File file = configFile();

    // Create and execute our Digester
    Digester digester = createStartDigester();

    try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
        InputStream inputStream = resource.getInputStream();
        InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
        inputSource.setByteStream(inputStream);
        digester.push(this);
        digester.parse(inputSource);
    } catch (Exception e) {
        if  (file == null) {
            log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml"), e);
        } else {
            log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
            if (file.exists() && !file.canRead()) {
                log.warn(sm.getString("catalina.incorrectPermissions"));
            }
        }
        return;
    }

    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();

    // Start the new server
    try {
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error(sm.getString("catalina.initError"), e);
        }
    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info(sm.getString("catalina.init", Long.valueOf((t2 - t1) / 1000000)));
    }
}
複製程式碼

這裡,這個server從哪裡來,我們從digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");中可以知道,其使用了這個類的例項,我們再回到digester.push(this); digester.parse(inputSource);這兩句程式碼上來,可知,未開始解析時先呼叫Digester.push(this),此時棧頂元素是Catalina,這個用來為catalina設定server,這裡,要對digester的解析來涉及下:

如解析到<Server>時就會建立StandardServer類的例項並反射呼叫Digesterstack棧頂物件的setter方法(呼叫的方法通過傳入的name值確定)。 digester中涉及的IntrospectionUtils.setProperty(top, name, value)方法,即top為棧頂物件,name為這個棧頂物件要設定的屬性名,value為要設定的屬性值。 剛開始時棧頂元素是Catalina,即呼叫Catalina.setServer(Server object)方法設定Server為後面呼叫Server.start()做準備,然後將StandardServer物件例項放入Digesterstack物件棧中。

getServer().init()

接下來,我們來看getServer().init(),由上知,我們去找org.apache.catalina.core.StandardServer.java這個類,其繼承LifecycleMBeanBase並實現了Server,通過LifecycleMBeanBase此類,說明這個StandardServer管理的生命週期,即通過LifecycleMBeanBase父類LifecycleBase實現的init()方法:

//org.apache.catalina.util.LifecycleBase.java

@Override
public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}
複製程式碼

於是,我們關注 initInternal()StandardServer中的實現,程式碼過多,這裡就把過程講下: 1、呼叫父類org.apache.catalina.util.LifecycleMBeanBase#initInternal方法,註冊MBean

2、註冊本類的其它屬性的MBean

3、NamingResources初始化 : globalNamingResources.init();

4、從common ClassLoader開始往上檢視,直到SystemClassLoader,遍歷各個classLoader對應的檢視路徑,找到jar結尾的檔案,讀取Manifest資訊,加入到ExtensionValidator#containerManifestResources屬性中。

5、初始化service,預設實現是StandardService。

   i) 呼叫super.initInternal()方法

ii) container初始化,這裡container例項是StandardEngine。 ​
iii) Executor初始化 ​
iv)Connector初始化: ​
​ a)org.apache.catalina.connector.Connector Connector[HTTP/1.1-8080] ​
​ b) org.apache.catalina.connector.Connector Connector[AJP/1.3-8009]

Catalina中start裡的getServer().start()解讀

這裡,我們可以看到StandardServer的父類org.apache.catalina.util.LifecycleBase.java的實現:

@Override
public final synchronized void start() throws LifecycleException {

    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {

        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }

        return;
    }

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            // This is a 'controlled' failure. The component put itself into the
            // FAILED state so call stop() to complete the clean-up.
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        // This is an 'uncontrolled' failure so put the component into the
        // FAILED state and throw an exception.
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}
複製程式碼

對於StandardServer,我們關注的是其對於startInternal();的實現,原始碼不貼了,具體過程如下: 1、觸發CONFIGURE_START_EVENT事件。

2、設定本物件狀態為STARTING

3、NameingResource啟動:globalNamingResources.start(); 4、StandardService啟動。

   i) 設定狀態為STARTING

ii) container啟動,即StandardEngine啟動 ​
iii) Executor 啟動 ​
iv) Connector啟動: ​
​ a)org.apache.catalina.connector.Connector Connector[HTTP/1.1-8080] ​
​ b) org.apache.catalina.connector.Connector Connector[AJP/1.3-8009]

終於,我們探究到了我要講的主角Connector

Connector解讀

Connector構造器

我們由apache-tomcat-9.0.14\conf目錄(此處請自行下載相應版本的tomcat)下的server.xml中的Connector配置可知,其預設8080埠的配置協議為HTTP/1.1

<Connector port="8080" protocol="HTTP/1.1"
            connectionTimeout="20000"
            redirectPort="8443" />
            <!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

複製程式碼

知道了這些,我們去看它的程式碼中的實現:

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

    // Default for Connector depends on this system property
    setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
複製程式碼

對於tomcat8.5以上,其預設就是Http11NioProtocol協議,這裡,我們給其設定了HTTP/1.1,但根據上面的if語句的判斷,是相等的,也就是最後還是選擇的Http11NioProtocol

Connector初始化與啟動

同樣,由上一節可知,我們會涉及到Connector初始化,也就是其也會繼承LifecycleMBeanBase,那麼,我們來看其相關initInternal()實現:

@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (protocolHandler == null) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"));
    }

    // Initialize adapter
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    if (service != null) {
        protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
    }

    // Make sure parseBodyMethodsSet has a default
    if (null == parseBodyMethodsSet) {
        setParseBodyMethods(getParseBodyMethods());
    }

    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
                getProtocolHandlerClassName()));
    }
    if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
            protocolHandler instanceof AbstractHttp11JsseProtocol) {
        AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
                (AbstractHttp11JsseProtocol<?>) protocolHandler;
        if (jsseProtocolHandler.isSSLEnabled() &&
                jsseProtocolHandler.getSslImplementationName() == null) {
            // OpenSSL is compatible with the JSSE configuration, so use it if APR is available
            jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
        }
    }

    try {
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}
複製程式碼

這裡涉及的過程如下: 1、註冊MBean

2、CoyoteAdapter例項化,CoyoteAdapter是請求的入口。當有請求時,CoyoteAdapter對狀態進行了處理,結尾處對請求進行回收,中間過程交由pipeline來處理。

3、protocolHandler 初始化(org.apache.coyote.http11.Http11Protocol)

在這一步中,完成了endpoint的初始化

關於啟動就不說了,其設定本物件狀態為STARTING,同時呼叫protocolHandler.start();,接下來,就要進入我們的核心節奏了。

@Override
protected void startInternal() throws LifecycleException {

    // Validate settings before starting
    if (getPortWithOffset() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
    }

    setState(LifecycleState.STARTING);

    try {
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}
複製程式碼

Protocol的相關解讀

這裡,我們直接從其抽象實現org.apache.coyote.AbstractProtocol.java來看,其也是遵循生命週期的,所以其也要繼承LifecycleMBeanBase並實現自己的init()start()等生命週期方法,其內部都是由相應的自實現的endpoint來執行具體邏輯:

//org.apache.coyote.AbstractProtocol.java
@Override
public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
        logPortOffset();
    }

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }

    if (this.domain != null) {
        rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        Registry.getRegistry(null, null).registerComponent(
                getHandler().getGlobal(), rgOname, null);
    }

    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);

    endpoint.init();
}


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

    endpoint.start();
    monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
            new Runnable() {
                @Override
                public void run() {
                    if (!isPaused()) {
                        startAsyncTimeout();
                    }
                }
            }, 0, 60, TimeUnit.SECONDS);
}
複製程式碼

org.apache.coyote.http11.Http11AprProtocol這個類來講,其接收的是NioEndpoint來進行構造器的實現,其內部的方法的具體實現也經由此NioEndpoint來實現其邏輯:

public class Http11NioProtocol extends AbstractHttp11JsseProtocol<NioChannel> {

    private static final Log log = LogFactory.getLog(Http11NioProtocol.class);


    public Http11NioProtocol() {
        super(new NioEndpoint());
    }


    @Override
    protected Log getLog() { return log; }


    // -------------------- Pool setup --------------------

    public void setPollerThreadCount(int count) {
        ((NioEndpoint)getEndpoint()).setPollerThreadCount(count);
    }

    public int getPollerThreadCount() {
        return ((NioEndpoint)getEndpoint()).getPollerThreadCount();
    }

    public void setSelectorTimeout(long timeout) {
        ((NioEndpoint)getEndpoint()).setSelectorTimeout(timeout);
    }

    public long getSelectorTimeout() {
        return ((NioEndpoint)getEndpoint()).getSelectorTimeout();
    }

    public void setPollerThreadPriority(int threadPriority) {
        ((NioEndpoint)getEndpoint()).setPollerThreadPriority(threadPriority);
    }

    public int getPollerThreadPriority() {
      return ((NioEndpoint)getEndpoint()).getPollerThreadPriority();
    }


    // ----------------------------------------------------- JMX related methods

    @Override
    protected String getNamePrefix() {
        if (isSSLEnabled()) {
            return "https-" + getSslImplementationShortName()+ "-nio";
        } else {
            return "http-nio";
        }
    }
}
複製程式碼

Endpoint相關解讀

這裡,EndPoint用於處理具體連線和傳輸資料,即用來實現網路連線和控制,它是伺服器對外I/O操作的接入點。主要任務是管理對外的socket連線,同時將建立好的socket連線交到合適的工作執行緒中去。 裡面兩個主要的屬性類是AcceptorPollerSocketProcessor。 我們以NioEndpoint為例,其內部請求處理具體的流程如下:

結合上一節最後,我們主要還是關注其對於Protocol有關生命週期方法的具體實現:

//org.apache.tomcat.util.net.AbstractEndpoint.java
public final void init() throws Exception {
    if (bindOnInit) {
        bindWithCleanup();
        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);

        ObjectName socketPropertiesOname = new ObjectName(domain +
                ":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
        socketProperties.setObjectName(socketPropertiesOname);
        Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);

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

public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bindWithCleanup();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}

//org.apache.tomcat.util.net.AbstractEndpoint.java
private void bindWithCleanup() throws Exception {
    try {
        bind();
    } catch (Throwable t) {
        // Ensure open sockets etc. are cleaned up if something goes
        // wrong during bind
        ExceptionUtils.handleThrowable(t);
        unbind();
        throw t;
    }
}
複製程式碼

這兩個方法主要呼叫bind(此處可以查閱bindWithCleanup()的具體實現) 和startlntemal 方法,它們是模板方法,可以自行根據需求實現,這裡,我們參考NioEndpoint 中的實現, bind 方法程式碼如下:

//org.apache.tomcat.util.net.NioEndpoint.java
@Override
public void bind() throws Exception {
    initServerSocket();

    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

    selectorPool.open();
}
複製程式碼

這裡的bind 方法中首先初始化了ServerSocket(這個東西我們在jdk網路程式設計裡都接觸過,就不多說了,這裡是封裝了一個工具類,看下面實現),然後檢查了代表AcceptorPoller 初始化的執行緒數量的acceptorThreadCount屬性和pollerThreadCount 屬性,它們的值至少為1。

// Separated out to make it easier for folks that extend NioEndpoint to
// implement custom [server]sockets
protected void initServerSocket() throws Exception {
    if (!getUseInheritedChannel()) {
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    serverSock.configureBlocking(true); //mimic APR behavior
}

複製程式碼

這裡,Acceptor 用於接收請求,將接收到請求交給Poller 處理,它們都是啟動執行緒來處理的。另外還進行了初始化SSL 等內容。NioEndpointstartInternal 方法程式碼如下:

/**
* The socket pollers.
*/
private Poller[] pollers = null;

/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@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());

        // Create worker collection
        if ( getExecutor() == null ) {
            createExecutor();
        }

        initializeConnectionLatch();

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

        startAcceptorThreads();
    }
}
複製程式碼

這裡首先初始化了一些屬性,初始化的屬性中的processorCacheSynchronizedStack<SocketProcessor>型別, SocketProcessorNioEndpoint 的一個內部類, Poller 接收到請求後就會交給它處理, SocketProcessor 又會將請求傳遞到Handler。 然後啟動了PollerAcceptor 來處理請求,這裡我們要注意的的是,pollers是一個數組,其管理了一堆Runnable,由前面可知,假如我們並沒有對其進行設定,那就是1,也就是說,其預設情況下只是一個單執行緒。這個執行緒創建出來後就將其設定為守護執行緒,直到tomcat容器結束,其自然也會跟著結束。 這裡,我們想要對其進行配置的話,可以在server.xml中進行相應設定:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"  
               connectionTimeout="20000"  
               maxHeaderCount="64"  
               maxParameterCount="64"  
               maxHttpHeaderSize="8192"  
               URIEncoding="UTF-8"  
               useBodyEncodingForURI="false"  
               maxThreads="128"  
               minSpareThreads="12"  
               acceptCount="1024"  
               connectionLinger="-1"  
               keepAliveTimeout="60"  
               maxKeepAliveRequests="32"  
               maxConnections="10000"  
               acceptorThreadCount="1"  
               pollerThreadCount="2"  
               selectorTimeout="1000"  
               useSendfile="true"  
               selectorPool.maxSelectors="128"  
               redirectPort="8443" />  
複製程式碼

啟動AcceptorstartAcceptorThreads 方法在 AbstractEndpoint 中,程式碼如下:

protected void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new ArrayList<>(count);

    for (int i = 0; i < count; i++) {
        Acceptor<U> acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor-" + i;
        acceptor.setThreadName(threadName);
        acceptors.add(acceptor);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}
複製程式碼

這裡的getAcceptorThreadCount 方法就是獲取的init 方法中處理過的acceptorThreadCount屬性,獲取到後就會啟動相應數量的Acceptor 執行緒來接收請求。默認同樣是1,其建立執行緒的方式和Poller一致,就不多說了。

這裡,我們再來看下webapps/docs/config/http.xml的文件說明:

<attribute name="acceptorThreadCount" required="false">
    <p>The number of threads to be used to accept connections. Increase this
    value on a multi CPU machine, although you would never really need more
    than <code>2</code>. Also, with a lot of non keep alive connections, you
    might want to increase this value as well. Default value is
    <code>1</code>.</p>
</attribute>

<attribute name="pollerThreadCount" required="false">
    <p>(int)The number of threads to be used to run for the polling events.
    Default value is <code>1</code> per processor but not more than 2.<br/>
    When accepting a socket, the operating system holds a global lock. So the benefit of
    going above 2 threads diminishes rapidly. Having more than one thread is for
    system that need to accept connections very rapidly. However usually just
    increasing <code>acceptCount</code> will solve that problem.
    Increasing this value may also be beneficial when a large amount of send file
    operations are going on.
    </p>
</attribute>
複製程式碼

由此可知,acceptorThreadCount用於設定接受連線的執行緒數。 在多CPU機器上增加這個值,雖然你可能真的不需要超過2個。哪怕有很多非keep alive連線,你也可能想要增加這個值。 其預設值為1。 pollerThreadCount用於為輪詢事件執行的執行緒數。預設值為每個處理器1個但不要超過2個(上面的優化配置裡的設定為2)。接受socket時,作業系統將保持全域性鎖定。 因此,超過2個執行緒的好處迅速減少。 當系統擁有多個該型別執行緒,它可以非常快速地接受連線。 所以增加acceptCount就可以解決這個問題。當正在進行大量傳送檔案操作時,增加此值也可能是有益的。

Acceptor和Poller的工作方式

我們先來看一張NioEndpoint處理的的時序圖:

Acceptor工作方式

我們由前面可知,Acceptor和Poller都實現了Runnable介面,所以其主要工作流程就在其實現的run方法內,這裡我們先來看Acceptor對於run方法的實現:

//org.apache.tomcat.util.net.NioEndpoint.java
@Override
protected SocketChannel serverSocketAccept() throws Exception {
    return serverSock.accept();
}
//org.apache.tomcat.util.net.Acceptor.java
public class Acceptor<U> implements Runnable {

    private static final Log log = LogFactory.getLog(Acceptor.class);
    private static final StringManager sm = StringManager.getManager(Acceptor.class);

    private static final int INITIAL_ERROR_DELAY = 50;
    private static final int MAX_ERROR_DELAY = 1600;

    private final AbstractEndpoint<?,U> endpoint;
    private String threadName;
    protected volatile AcceptorState state = AcceptorState.NEW;


    public Acceptor(AbstractEndpoint<?,U> endpoint) {
        this.endpoint = endpoint;
    }


    public final AcceptorState getState() {
        return state;
    }


    final void setThreadName(final String threadName) {
        this.threadName = threadName;
    }


    final String getThreadName() {
        return threadName;
    }


    @Override
    public void run() {

        int errorDelay = 0;

        // Loop until we receive a shutdown command
        while (endpoint.isRunning()) {

            // Loop if endpoint is paused
            while (endpoint.isPaused() && endpoint.isRunning()) {
                state = AcceptorState.PAUSED;
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // Ignore
                }
            }

            if (!endpoint.isRunning()) {
                break;
            }
            state = AcceptorState.RUNNING;

            try {
                //if we have reached max connections, wait
                endpoint.countUpOrAwaitConnection();

                // Endpoint might have been paused while waiting for latch
                // If that is the case, don't accept new connections
                if (endpoint.isPaused()) {
                    continue;
                }

                U socket = null;
                try {
                    // Accept the next incoming connection from the server
                    // socket
                    // 建立一個socketChannel,接收下一個從伺服器進來的連線  
                    socket = endpoint.serverSocketAccept();
                } catch (Exception ioe) {
                    // We didn't get a socket
                    endpoint.countDownConnection();
                    if (endpoint.isRunning()) {
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    } else {
                        break;
                    }
                }
                // Successful accept, reset the error delay
                errorDelay = 0;

                // Configure the socket
                // 如果EndPoint處於running狀態並且沒有沒暫停
                if (endpoint.isRunning() && !endpoint.isPaused()) {
                    // setSocketOptions() will hand the socket o