Tomcat原始碼分析2 之 Protocol實現分析

weixin_34087301發表於2017-05-29

簡介

本文繼續以tomcat8 為例,簡單分析下Tomcat的幾種protocol的差異,以及處理連結的細節。
  主要有以下內容:</br>
  - tomcat protocol的分類</br>
  - tomcat protocol的實現</br>
  - 各個protocol的差異</br>

Tomcat protocol 配置

參考官方文件,tomcat protocol配置,可以看到protocol主要是有四種,預設使用的HTTP/1.1 ,對於tomcat8 以更高版本來說,HTTP/1.1的配置會預設使用nio來處理,也就是org.apache.coyote.http11.Http11NioProtocol

其他的幾種:</br>
  org.apache.coyote.http11.Http11Protocol java的bio connector,使用ServerSocket處理請求。</br>
  org.apache.coyote.http11.Http11NioProtocol java的nio connector,使用SocketChannel處理請求.</br>
  org.apache.coyote.http11.Http11Nio2Protocol java7新出的aio connector,使用AsynchronousSocketChannel處理請求。</br>
  org.apache.coyote.http11.Http11Nio2Protocol tomcat的native library connector。(這個我們稍後再講)</br>
  
  對於tomcat8 以更高版本來說,HTTP/1.1的配置會預設使用nio來處理,也就是
org.apache.coyote.http11.Http11NioProtocol。由org.apache.catalina.connector.ConnectorsetProtocol()方法也可以看到:

    public void setProtocol(String protocol) {
        //如果配置了apr,會預設使用apr(apr後面再講)
        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
           //這裡可以看到,HTTP/1.1是server.xml的預設配置,會預設使用nio處理
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11NioProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpNioProtocol");
            } else if (protocol != null) {
            //其他情況下,使用指定的protocol
                setProtocolHandlerClassName(protocol);
            }
        }

    }

Tomcat protocol的處理流程

ConnectorstartInternal()方法會啟動protocol

    @Override
    protected void startInternal() throws LifecycleException {

        // Validate settings before starting
        ...
        try {
            protocolHandler.start();
        } catch (Exception e) {
            ...
        }
    }

這裡的protocolHandler,就是上面setProtocol()方法指定的protocol。暫時以Http11NioProtocol為例,分析下請求處理流程。上面startInternal()方法接下來會到org.apache.coyote.AbstractProtocolstart()方法。

    @Override
    public void start() throws Exception {
        if (getLog().isInfoEnabled())
            getLog().info(sm.getString("abstractProtocolHandler.start",
                    getName()));
        try {
            //每個protocol都有一個對應的enpoint,最終是由endpoint來負責處理連結的。
            endpoint.start();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.startError",
                    getName()), ex);
            throw ex;
        }
    }

每個protocol對應了一個Endpoint</br>
  Http11Protocol對應org.apache.tomcat.util.net.JIoEndpoint</br>
  Http11NioProtocol對應org.apache.tomcat.util.net.NioEndpoint</br>
  Http11Nio2Protocol對應org.apache.tomcat.util.net.Nio2Endpoint</br>
  繼續檢視Nio2EndpointstartInternal()方法.

    @Override
    public void startInternal() throws Exception {

        if (!running) {
            ...
            //初始化最大連線數限制,在server.xml中可配置
            initializeConnectionLatch();

            // Start poller threads
            // poller 主要負責檢查各個 Selector 的狀態以及處理超時等
            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();
        }
    }

startAcceptorThreads()方法會啟動Endpoint內部的Acceptor。繼續檢視org.apache.tomcat.util.net.NioEndpoint.Acceptorrun()方法:

        @Override
        public void run() {
            ...
            // Loop until we receive a shutdown command
            while (running) {
                ...
                try {
                    //檢查下當前是否已經達到了最大連結數
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    SocketChannel socket = null;
                    try {
                        // Accept the next incoming connection from the server socket
                        socket = serverSock.accept();
                    } catch (IOException ioe) {
                       ...
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // setSocketOptions() will add channel to the poller if successful
                    // setSocketOptions 會把channel新增到poller
                    if (running && !paused) {
                        if (!setSocketOptions(socket)) {
                            countDownConnection();
                            closeSocket(socket);
                        }
                    } else {
                        countDownConnection();
                        closeSocket(socket);
                    }
                } ...
            }
            state = AcceptorState.ENDED;
        }
    }

從程式碼中可以看到,使用 SocketChannel 接受連線,然後通過 setSocketOptions 方法把 channel 交給 poller 處理。setSocketOptions(SocketChannel socket) 方法則是真正處理連結的地方。

    protected boolean setSocketOptions(SocketChannel socket) {
        // Process the connection
        try {
            //disable blocking, APR style, we are gonna be polling it
            socket.configureBlocking(false);
            Socket sock = socket.socket();
            socketProperties.setProperties(sock);
            NioChannel channel = nioChannels.pop();
            ...
            //會把 呼叫poller的 register 方法,把 channel 交給 poller
            getPoller0().register(channel);
        } catch (Throwable t) {
           ...
        }
        return true;
    }

getPoller0() 會根據設定的poll執行緒數,返回一個當前可用的 Poller 物件,使用Round robin演算法實現一個簡單的負載均衡。

    public Poller getPoller0() {
        int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
        return pollers[idx];
    }

接下來檢視 register()方法

        public void register(final NioChannel socket) {
            socket.setPoller(this);
            // 新建一個 KeyAttachment 物件,儲存跟socket相關的一些資訊
            KeyAttachment ka = new KeyAttachment(socket);
            ka.setPoller(this);
            ka.setTimeout(getSocketProperties().getSoTimeout());
            ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            ka.setSecure(isSSLEnabled());
            PollerEvent r = eventCache.pop();

            ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
            else r.reset(socket,ka,OP_REGISTER);
            //比較重要的是這句,把 r 新增到當前的 events 佇列中。
            addEvent(r);
        }

上面可以看到,每個請求進來之後,會通過 register 方法建立一個 PollerEvent 物件並新增到當前的 SynchronizedQueue<PollerEvent> events 佇列中。
接下來看 Poller的run()方法,上面已經說過,Poller 在startInternal()方法呼叫的時候建立並啟動。

        @Override
        public void run() {
            // Loop until destroy() is called
            while (true) {
                try {
                    ...
                    boolean hasEvents = false;
                    // Time to terminate?
                    if (close) {
                        events();
                        timeout(0, false);
                        ...
                        break;
                    } else {
                        //呼叫 events() 方法
                        hasEvents = events();
                    }
                }
              ...
            }//while
            stopLatch.countDown();
        }

接下來檢視events()方法

        public boolean events() {
            boolean result = false;
            PollerEvent pe = null;
            while ( (pe = events.poll()) != null ) {
                result = true;
                try {
                    //呼叫 PollerEvent 的 run() 方法
                    pe.run();
                    pe.reset();
                    if (running && !paused) {
                        eventCache.push(pe);
                    }
                } catch ( Throwable x ) {
                    log.error("",x);
                }
            }
            return result;
        }

可以看到,在tomcat啟動之後,會啟動相應的endpoint的Acceptor來接受請求,同時啟動相應的Poller來處理請求。</br>
  PollerEvent的run方法

        @Override
        public void run() {
            //對於新增的連結,會註冊給 poller 的 selector 處理。
            if ( interestOps == OP_REGISTER ) {
                try {
                //把當前的key註冊給poller中的selector物件,準備後續處理。
                    socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
                } catch (Exception x) {
                    log.error("", x);
                }
            } else {
                ...
            }//end if
        }//run

再回過來看 Poller 的 run() 方法。裡面有一段

                    while (iterator != null && iterator.hasNext()) {
                        SelectionKey sk = iterator.next();
                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
                        // Attachment may be null if another thread has called
                        // cancelledKey()
                        if (attachment == null) {
                            iterator.remove();
                        } else {
                            attachment.access();
                            iterator.remove();
                            processKey(sk, attachment);
                        }
                    }//while

會檢查selector所有的key,然後處理呼叫processKey()進行處理。
processKey() 會根據配置的Executer呼叫SocketProcessorrun() 方法.

接下來看SocketProcessorrun()方法最終會呼叫doRun()方法,在doRun()裡有如下程式碼:

        if (status == null) {
            state = handler.process(ka, SocketStatus.OPEN_READ);
       } else {
          state = handler.process(ka, status);
       }

接下來會呼叫到handlerprocess方法,這個handler就是在Http11NioProtocol的構造方法裡由setHandler設定的handler,也就是Http11ConnectionHandler 繼承了 AbstractConnectionHandler,接著來到 org.apache.coyote.AbstractProtocol.AbstractConnectionHandlerprocess方法,這裡會對每個socket做處理。包括呼叫具體的servlet處理業務等等。

總結

tomcat在啟動的時候,根據配置的protocol,啟動不同的Endpoint中的Acceptor 和 Poller。Acceptor負責接受請求,Poller負責呼叫執行緒池執行業務。

相關文章