Tomcat請求處理流程

上一篇說到了Tomcat的啟動流程于个,那么請求處理的流程是如何完成的呢模她?

Connector接收請求

Connector是請求接收器,通過設置的協(xié)議處理器處理請求缭保,以默認的Http11NioProtocol協(xié)議為例遥缕,Http11NioProtocol構(gòu)造時創(chuàng)建了NioEndpoint侥祭,用來處理網(wǎng)絡請求:

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

Connector啟動時,啟動了protocolHandler(Http11NioProtocol), protocolHandler啟動的時候又對endpoint進行了啟動齐婴,NioEndpoint啟動過程中綁定了本地端口并在Acceptor線程中監(jiān)聽網(wǎng)絡連接(詳情可見Tomcat啟動流程)油猫,所以Tomcat接收網(wǎng)絡請求的起點是從Accetpor線程開始的。
Acceptor中會循環(huán)阻塞調(diào)用serverSock.accept()來監(jiān)聽請求:

......
try {
                    // Accept the next incoming connection from the server
                    // socket
                    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
                if (endpoint.isRunning() && !endpoint.isPaused()) {
                    // setSocketOptions() will hand the socket off to
                    // an appropriate processor if successful
                    if (!endpoint.setSocketOptions(socket)) {
                        endpoint.closeSocket(socket);
                    }
                } else {
                    endpoint.destroySocket(socket);
                }
......

接收到請求后尔店,通過setSocketOptions來處理請求:

protected boolean setSocketOptions(SocketChannel socket) {
        NioSocketWrapper socketWrapper = null;
        try {
            // Allocate channel and wrapper
            NioChannel channel = null;
            if (nioChannels != null) {
                channel = nioChannels.pop();
            }
            if (channel == null) {
                SocketBufferHandler bufhandler = new SocketBufferHandler(
                        socketProperties.getAppReadBufSize(),
                        socketProperties.getAppWriteBufSize(),
                        socketProperties.getDirectBuffer());
                if (isSSLEnabled()) {
                    channel = new SecureNioChannel(bufhandler, selectorPool, this);
                } else {
                    channel = new NioChannel(bufhandler);
                }
            }
            NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
            channel.reset(socket, newWrapper);
            connections.put(socket, newWrapper);
            socketWrapper = newWrapper;

            // Set socket properties
            // Disable blocking, polling will be used
            socket.configureBlocking(false);
            socketProperties.setProperties(socket.socket());

            socketWrapper.setReadTimeout(getConnectionTimeout());
            socketWrapper.setWriteTimeout(getConnectionTimeout());
            socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            socketWrapper.setSecure(isSSLEnabled());
            poller.register(channel, socketWrapper);
            return true;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            try {
                log.error(sm.getString("endpoint.socketOptionsError"), t);
            } catch (Throwable tt) {
                ExceptionUtils.handleThrowable(tt);
            }
            if (socketWrapper == null) {
                destroySocket(socket);
            }
        }
        // Tell to close the socket if needed
        return false;
    }

代碼比較長眨攘,最關鍵的代碼是poller.register(channel, socketWrapper)主慰,將請求進來的socket包裝后注冊到poller中等待讀寫事件嚣州,這個地方的socket是非阻塞的鲫售,即可以用一個poller線程處理大量的請求連接,提高系統(tǒng)吞吐量该肴。
接下來來到Poller類:

public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) {
            socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            PollerEvent r = null;
            if (eventCache != null) {
                r = eventCache.pop();
            }
            if (r == null) {
                r = new PollerEvent(socket, OP_REGISTER);
            } else {
                r.reset(socket, OP_REGISTER);
            }
            addEvent(r);
        }

Poller線程run方法:

@Override
        public void run() {
            // Loop until destroy() is called
            while (true) {

                boolean hasEvents = false;

                try {
                    if (!close) {
                        hasEvents = events();
                       ...
                            keyCount = selector.select(selectorTimeout);
                        ...
                        wakeupCounter.set(0);
                    }
                   ...
                } catch (Throwable x) {
                    ExceptionUtils.handleThrowable(x);
                    log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
                    continue;
                }
                // Either we timed out or we woke up, process events first
                if (keyCount == 0) {
                    hasEvents = (hasEvents | events());
                }

                Iterator<SelectionKey> iterator =
                    keyCount > 0 ? selector.selectedKeys().iterator() : null;
                // Walk through the collection of ready keys and dispatch
                // any active event.
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    processKey(sk, socketWrapper);
                }

代碼進行了一些刪減情竹,只保留了主要流程:
1.Poller注冊socket
Poller將連接上的socket設置interestOps(SelectionKey.OP_READ),包裝成PollerEvent匀哄,interestOps為OP_REGISTER秦效,放入隊列中。
2.Poller線程從隊列中取事件涎嚼,執(zhí)行run方法:

if (interestOps == OP_REGISTER) {
                try {
                    socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper());
                } catch (Exception x) {
                    log.error(sm.getString("endpoint.nio.registerFail"), x);
                }
            } 

將socket注冊到Poller的Selector中阱州,ops為SelectionKey.OP_READ,等待對端發(fā)送數(shù)據(jù)法梯。
3.然后在socket有數(shù)據(jù)可讀時苔货,通過processKey(sk, socketWrapper)來進行處理。繼續(xù)跟進processKey立哑,最終將socket封裝成socketProcessor提交到Executor處理夜惭。

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = null;
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }
......

SocketProcessor實現(xiàn)了Runnable接口,在線程池中執(zhí)行SocketProcessor.doRun,忽略可能的TLS握手到流程铛绰,將通過連接處理器ConnectionHandler來處理請求:

getHandler().process(socketWrapper, SocketEvent.OPEN_READ);

ConnectHandler又通過Http11Processor來處理

if (processor == null) {
                    processor = getProtocol().createProcessor();
                    register(processor);
                }

               ...
                // Associate the processor with the connection
                wrapper.setCurrentProcessor(processor);

                SocketState state = SocketState.CLOSED;
                do {
                    state = processor.process(wrapper, status);

Http11Processor中會根據(jù)Http協(xié)議來解析數(shù)據(jù)诈茧,封裝成request、response模型捂掰,并通過適配器轉(zhuǎn)給Container敢会,然后Connector的任務就完成了,接下來的操作由Container來進行这嚣。

getAdapter().service(request, response);

Adapter的處理

Connect的主要任務是接收請求鸥昏,并在有數(shù)據(jù)讀寫時在線程池中根據(jù)使用的協(xié)議來解析數(shù)據(jù)并封裝成request、response疤苹,然后交給Adapter來處理互广。
Adapter的實現(xiàn)類是CoyoteAdapter,顧名思義是一個適配器卧土,將Connector連接器讀取的數(shù)據(jù)適配到Container容器來處理惫皱。
Adapter主要有兩個任務map和轉(zhuǎn)發(fā)。
map在postParseRequest方法中進行:

connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());

根據(jù)請求到uri找到目標Host尤莺、Context旅敷、Wrapper。
Mapper對象存在service中颤霎,通過MapperListener來監(jiān)聽Container到生命周期媳谁,動態(tài)調(diào)整Mapper中的Container涂滴。
通過Mapper找到目標Container后,就可以轉(zhuǎn)發(fā)給Container來處理了:

connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

Container的處理流程

Adapter傳給Container的操作是將request晴音、response參數(shù)交給engine的pipeline處理柔纵。每一個級別的Container都維護了一個pipeline,用來處理請求锤躁,這是典型的流水線模式搁料,pipeline中維護了一個Valve鏈表,請求在pipeline中處理的過程就是從第一個Valve處理到最后一個系羞,Valve可以方便的進行配置來實現(xiàn)各層容器的擴展功能郭计,每個pipeline的最后一個Valve是"basic",完成基本的邏輯處理椒振,如Engine的basic將調(diào)用傳給下一級的Host的pipeline昭伸,Host的basic將調(diào)用傳給下一級的Context的pipeline,Context的basic將調(diào)用傳給下一級的Wrapper的pipeline澎迎,如Engine的basic StandardEngineValve:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            // HTTP 0.9 or HTTP 1.0 request without a host when no default host
            // is defined. This is handled by the CoyoteAdapter.
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);
    }

而Wrapper作為最底層的Container庐杨,basic完成最終Servlet的加載、初始化嗡善,并組裝filterChain進行調(diào)用:
standardWrapperValve.invoke部分代碼

...
if (!unavailable) {
  servlet = wrapper.allocate();
}
...
// Create the filter chain for this request
ApplicationFilterChain filterChain =
  ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
...
filterChain.doFilter(request.getRequest(), response.getResponse());
...

那么什么時候調(diào)用了Servlet的service來處理請求呢辑莫?在filter鏈的調(diào)用過程中,鏈中所有的filter處理完成后罩引,后執(zhí)行Servlet.service來處理請求:

private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ...
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        //chain處理完成后各吨,執(zhí)行servlet.service
        try {
           ...
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
               ...
            } else {
                servlet.service(request, response);
            }

至此,tomcat已經(jīng)完成了從接受連接請求到找到目標servlet來處理請求的流程袁铐,實現(xiàn)了作為Servlet容器的功能揭蜒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市剔桨,隨后出現(xiàn)的幾起案子屉更,更是在濱河造成了極大的恐慌,老刑警劉巖洒缀,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瑰谜,死亡現(xiàn)場離奇詭異,居然都是意外死亡树绩,警方通過查閱死者的電腦和手機萨脑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饺饭,“玉大人渤早,你說我怎么就攤上這事√笨。” “怎么了鹊杖?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵悴灵,是天一觀的道長。 經(jīng)常有香客問我骂蓖,道長积瞒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任涯竟,我火速辦了婚禮赡鲜,結(jié)果婚禮上空厌,老公的妹妹穿的比我還像新娘庐船。我一直安慰自己,他們只是感情好嘲更,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布筐钟。 她就那樣靜靜地躺著,像睡著了一般赋朦。 火紅的嫁衣襯著肌膚如雪篓冲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天宠哄,我揣著相機與錄音壹将,去河邊找鬼。 笑死毛嫉,一個胖子當著我的面吹牛诽俯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播承粤,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼暴区,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辛臊?” 一聲冷哼從身側(cè)響起仙粱,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彻舰,沒想到半個月后伐割,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡刃唤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年隔心,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片透揣。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡济炎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辐真,到底是詐尸還是另有隱情须尚,我是刑警寧澤崖堤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站耐床,受9級特大地震影響密幔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撩轰,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一胯甩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堪嫂,春花似錦偎箫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至恶复,卻和暖如春怜森,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谤牡。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工副硅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翅萤。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓恐疲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親断序。 傳聞我的和親對象是個殘疾皇子流纹,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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