Tomcat 源碼分析 (二) : Connector

NIOEndPoint

NIOEndPointbind()方法開啟一個SocketServer

    @Override
    public void bind() throws Exception {
         //開啟一個server socket
        serverSock = ServerSocketChannel.open();
        //根據(jù)配置文件設置server socket的屬性
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        serverSock.socket().bind(addr,getAcceptCount());
        serverSock.configureBlocking(true); //mimic APR behavior

        // 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;
        }
        stopLatch = new CountDownLatch(pollerThreadCount);

        // 如果需要的話,初始化SSL
        initialiseSsl();
        //開啟Selector,堅挺NIO的 IO的事件
        selectorPool.open();
    }

·

Acceptor線程接收客戶請求

在Tomcat啟動的時候會啟動一個Endpoint,并會調(diào)用它的startInternal方法,在這里開啟了一個Acceptor的子線程。利用這個Acceptor子線程來接收Client端的Socket連接

   @Override
    public void run() {
            ........................省略代碼................................................
           // 這里調(diào)用上面NIOEndPoint 的serverSocketChannel 來接收一個客戶端發(fā)送來的socket
           socket = endpoint.serverSocketAccept();
           ........................省略代碼................................................
          // setSocketOptions() will hand the socket off to
         // an appropriate processor if successful
         if (!endpoint.setSocketOptions(socket)) {
                //及時關閉Socket連接
               endpoint.closeSocket(socket);  
          }
    }

從Tomcat8 以后,Tomcat的默認連接為NIO。所以在這里的EndPoint的具體實現(xiàn)是NioEndPoint

    @Override
    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();
            if (channel == null) {
                SocketBufferHandler bufhandler = new SocketBufferHandler(
                        socketProperties.getAppReadBufSize(),
                        socketProperties.getAppWriteBufSize(),
                        socketProperties.getDirectBuffer());
                if (isSSLEnabled()) {
                    channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
                } else {
                    // NIOChannel 實質(zhì)上對ByteChannel 的一個封裝實現(xiàn)
                    channel = new NioChannel(socket, bufhandler);
                }
            } else {
                // 根據(jù)Socket,對設置當前的NioChannel
                channel.setIOChannel(socket);
                channel.reset();
            }
            getPoller0().register(channel);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            try {
                log.error("",t);
            } catch (Throwable tt) {
                ExceptionUtils.handleThrowable(tt);
            }
            // Tell to close the socket
            return false;
        }
        return true;
    }

通過閱讀代碼可以看出其處理過程如下:

  1. 設置非阻塞,以及其他的一些參數(shù)如SoTimeout、ReceiveBufferSize、SendBufferSize

  2. 然后將SocketChannel封裝成一個NioChannel,封裝過程使用了緩存,即避免了重復創(chuàng)建NioChannel,封裝過程中使用了緩存,既避免了重復創(chuàng)建NioChannel對象,直接利用原有的NIOChannel,并將NioChannel中的數(shù)據(jù)全部清空。

  3. 選擇一個Poller進行注冊

再來看一下Poller

  public void register(final NioChannel socket) {
            socket.setPoller(this);
            NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
            socket.setSocketWrapper(ka);
            ka.setPoller(this);
            ka.setReadTimeout(getConnectionTimeout());
            ka.setWriteTimeout(getConnectionTimeout());
            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); //
            addEvent(r); //將event 加入到事件處理隊列中
        }

這里又是進行一些參數(shù)包裝,將socket和Poller的關系綁定,再次從緩存中取出或者重新構建一個PollerEvent,然后將該event放到Poller的事件隊列中等待被異步處理

再來看看對于被加入到隊列中的events的處理

        public boolean events() {
            boolean result = false;

            PollerEvent pe = null;
            while ( (pe = events.poll()) != null ) {
                result = true;
                try {
                    pe.run(); //開始處理這個pollerEvent
                    pe.reset(); //
                    if (running && !paused) {
                        eventCache.push(pe);
                    }
                } catch ( Throwable x ) {
                    log.error("",x);
                }
            }

            return result;
        }

Poller的run 方法會對

   @Override
        public void run() {
.........................省略代碼................................

                // 遍歷并處理 已經(jīng)準備好的NIO 事件
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();

                    if (attachment == null) {
                        iterator.remove();
                    } else {
                        iterator.remove();
                        //將已經(jīng)準備好的IO事件和 綁定的Socket進行分類處理
                        processKey(sk, attachment);
                    }
                }//while
  ............................省略代碼.............
}
     protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
            try {
                if ( close ) {
                    cancelledKey(sk);
                } else if ( sk.isValid() && attachment != null ) {
                    if (sk.isReadable() || sk.isWritable() ) {
                        if ( attachment.getSendfileData() != null ) {
                            processSendfile(sk,attachment, false);
                        } else {
                            //為了避免多線程對于attach的socketChannel處理的沖突,在這里將socketChannel的
                            // ready 操作位取反
                            unreg(sk, attachment, sk.readyOps());
                            boolean closeSocket = false;
                            // 如果SelectorKey 為Read,那就去處理Read
                            if (sk.isReadable()) {
                                if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
                                    closeSocket = true;
                                }
                            }
                            // 如果SelectorKey 為Read,那就去處理Write
                            if (!closeSocket && sk.isWritable()) {
                                if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
                                    closeSocket = true;
                                }
                            }
                            if (closeSocket) {
                                // 將attach的socketChannel從key中解綁
                                cancelledKey(sk);
                            }
                        }
                    }
                } else {
                    //invalid key
                    cancelledKey(sk);
                }
            } catch ( CancelledKeyException ckx ) {
                cancelledKey(sk);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error("",t);
            }
        }
   /**
     * Process the given SocketWrapper with the given status. Used to trigger
     * processing as if the Poller (for those endpoints that have one)
     * selected the socket.
     *
     * @param socketWrapper The socket wrapper to process
     * @param event         The socket event to be processed
     * @param dispatch      Should the processing be performed on a new
     *                          container thread
     *
     * @return if processing was triggered successfully
     */
    public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = processorCache.pop();
            if (sc == null) {
                
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
              //將SocketProcessorBase交給線程池中的線程來處理
                executor.execute(sc);
            } else {
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }

線程池中的線程即為 NioEndPonint$SocketProcessor 內(nèi)部類來看一下其內(nèi)部的doRun()方法

doRun{
  .....................
    //如果握手已經(jīng)完成....
    if (handshake == 0) {
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        // 這里是關鍵
                        state = getHandler().process(socketWrapper, event);
                    }
                    if (state == SocketState.CLOSED) {
                        close(socket, key);
                    }
                }
    ....................
}

從上面代碼中可以看出,注意它調(diào)用了hanler.process(socket)來生成響應數(shù)據(jù)。并且根據(jù)處理之后的返回狀態(tài)來決定是否關閉Socket連接

對于handler.process(socket)的處理。

NioEndpoint類中的Handler接口的具體實現(xiàn)是靜態(tài)類 AbstractProtocol$ConnectionHandler<S>通過查看其process(socket)方法.這個方法邏輯很長并且很復雜。說多了也沒用。
它在這個方法里做的動作

  1. 通過協(xié)議類型得到相應的Socket的 Processor,并cache起來,在這里這個Processor就是Http11Processor
  2. 通過一個ThreadLocal 類標識現(xiàn)在的這個線程正在處理一個請求
  3. 調(diào)用Processor的process方法。

上面的Processor的process方法通過抽象類間接的調(diào)用了Http11Processorservice()方法。這個service()方法也是相當復雜`
它主要完成的動作有

  • 填充Request,Reponse屬性
  • 調(diào)用CoyoteAdapter的service()方法

通過以上不走,一個請求連接就從Connector走到了Container

小結:

實現(xiàn)一個tomcat連接器Connector就是實現(xiàn)ProtocolHander接口的過程。Connector用來接收Socket Client端的請求,通過內(nèi)置的線程池去調(diào)用Servlet Container生成響應結果,并將響應結果同步或異步的返回給Socket Client。在第三方應用集成tomcat作為Web容器時,一般不會動Servlet Container端的代碼,那么connector的性能將是整個Web容器性能的關鍵。

Tomcat 對于Socket的處理.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內(nèi)容