Tomcat源碼分析2 之 Protocol實現(xiàn)分析

簡介

本文繼續(xù)以tomcat8 為例,簡單分析下Tomcat的幾種protocol的差異忌穿,以及處理鏈接的細(xì)節(jié)。
  主要有以下內(nèi)容:</br>
  - tomcat protocol的分類</br>
  - tomcat protocol的實現(xiàn)</br>
  - 各個protocol的差異</br>

Tomcat protocol 配置

參考官方文檔,tomcat protocol配置,可以看到protocol主要是有四種碧注,默認(rèn)使用的HTTP/1.1 ,對于tomcat8 以更高版本來說竖哩,HTTP/1.1的配置會默認(rèn)使用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的配置會默認(rèn)使用nio來處理,也就是
org.apache.coyote.http11.Http11NioProtocol骇两。由org.apache.catalina.connector.ConnectorsetProtocol()方法也可以看到:

    public void setProtocol(String protocol) {
        //如果配置了apr速种,會默認(rèn)使用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的默認(rèn)配置低千,會默認(rèn)使用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都有一個對應(yīng)的enpoint舍沙,最終是由endpoint來負(fù)責(zé)處理鏈接的。
            endpoint.start();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.startError",
                    getName()), ex);
            throw ex;
        }
    }

每個protocol對應(yīng)了一個Endpoint</br>
  Http11Protocol對應(yīng)org.apache.tomcat.util.net.JIoEndpoint</br>
  Http11NioProtocol對應(yīng)org.apache.tomcat.util.net.NioEndpoint</br>
  Http11Nio2Protocol對應(yīng)org.apache.tomcat.util.net.Nio2Endpoint</br>
  繼續(xù)查看Nio2EndpointstartInternal()方法.

    @Override
    public void startInternal() throws Exception {

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

            // Start poller threads
            // poller 主要負(fù)責(zé)檢查各個 Selector 的狀態(tài)以及處理超時等
            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內(nèi)部的Acceptor拂铡。繼續(xù)查看org.apache.tomcat.util.net.NioEndpoint.Acceptorrun()方法:

        @Override
        public void run() {
            ...
            // Loop until we receive a shutdown command
            while (running) {
                ...
                try {
                    //檢查下當(dāng)前是否已經(jīng)達(dá)到了最大鏈接數(shù)
                    //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();
            ...
            //會把 調(diào)用poller的 register 方法,把 channel 交給 poller
            getPoller0().register(channel);
        } catch (Throwable t) {
           ...
        }
        return true;
    }

getPoller0() 會根據(jù)設(shè)置的poll線程數(shù)失球,返回一個當(dāng)前可用的 Poller 對象岖是,使用Round robin算法實現(xiàn)一個簡單的負(fù)載均衡。

    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相關(guān)的一些信息
            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 添加到當(dāng)前的 events 隊列中。
            addEvent(r);
        }

上面可以看到黔牵,每個請求進(jìn)來之后聪轿,會通過 register 方法創(chuàng)建一個 PollerEvent 對象并添加到當(dāng)前的 SynchronizedQueue<PollerEvent> events 隊列中。
接下來看 Poller的run()方法猾浦,上面已經(jīng)說過陆错,Poller 在startInternal()方法調(diào)用的時候創(chuàng)建并啟動。

        @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 {
                        //調(diào)用 events() 方法
                        hasEvents = events();
                    }
                }
              ...
            }//while
            stopLatch.countDown();
        }

接下來查看events()方法

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

可以看到金赦,在tomcat啟動之后音瓷,會啟動相應(yīng)的endpoint的Acceptor來接受請求,同時啟動相應(yīng)的Poller來處理請求夹抗。</br>
  PollerEvent的run方法

        @Override
        public void run() {
            //對于新增的鏈接绳慎,會注冊給 poller 的 selector 處理。
            if ( interestOps == OP_REGISTER ) {
                try {
                //把當(dāng)前的key注冊給poller中的selector對象漠烧,準(zhǔn)備后續(xù)處理杏愤。
                    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沽甥,然后處理調(diào)用processKey()進(jìn)行處理。
processKey() 會根據(jù)配置的Executer調(diào)用SocketProcessorrun() 方法.

接下來看SocketProcessorrun()方法最終會調(diào)用doRun()方法乏奥,在doRun()里有如下代碼:

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

接下來會調(diào)用到handlerprocess方法摆舟,這個handler就是在Http11NioProtocol的構(gòu)造方法里由setHandler設(shè)置的handler,也就是Http11ConnectionHandler 繼承了 AbstractConnectionHandler,接著來到 org.apache.coyote.AbstractProtocol.AbstractConnectionHandlerprocess方法邓了,這里會對每個socket做處理恨诱。包括調(diào)用具體的servlet處理業(yè)務(wù)等等。

總結(jié)

tomcat在啟動的時候骗炉,根據(jù)配置的protocol照宝,啟動不同的Endpoint中的Acceptor 和 Poller。Acceptor負(fù)責(zé)接受請求句葵,Poller負(fù)責(zé)調(diào)用線程池執(zhí)行業(yè)務(wù)厕鹃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兢仰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剂碴,更是在濱河造成了極大的恐慌把将,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忆矛,死亡現(xiàn)場離奇詭異察蹲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)催训,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門洽议,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人漫拭,你說我怎么就攤上這事亚兄。” “怎么了嫂侍?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵儿捧,是天一觀的道長。 經(jīng)常有香客問我挑宠,道長菲盾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任各淀,我火速辦了婚禮懒鉴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碎浇。我一直安慰自己临谱,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布奴璃。 她就那樣靜靜地躺著悉默,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苟穆。 梳的紋絲不亂的頭發(fā)上抄课,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機(jī)與錄音雳旅,去河邊找鬼跟磨。 笑死,一個胖子當(dāng)著我的面吹牛攒盈,可吹牛的內(nèi)容都是我干的抵拘。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼型豁,長吁一口氣:“原來是場噩夢啊……” “哼僵蛛!你這毒婦竟也來了尚蝌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤墩瞳,失蹤者是張志新(化名)和其女友劉穎驼壶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喉酌,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡热凹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泪电。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片般妙。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖相速,靈堂內(nèi)的尸體忽然破棺而出碟渺,到底是詐尸還是另有隱情,我是刑警寧澤突诬,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布苫拍,位于F島的核電站,受9級特大地震影響旺隙,放射性物質(zhì)發(fā)生泄漏绒极。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一蔬捷、第九天 我趴在偏房一處隱蔽的房頂上張望垄提。 院中可真熱鬧,春花似錦周拐、人聲如沸铡俐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽审丘。三九已至,卻和暖如春勾给,著一層夾襖步出監(jiān)牢的瞬間滩报,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工锦秒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留露泊,地道東北人喉镰。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓旅择,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侣姆。 傳聞我的和親對象是個殘疾皇子生真,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

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