<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Tomcat 第四篇:請(qǐng)求處理流程(上)

          共 9167字,需瀏覽 19分鐘

           ·

          2020-09-29 14:08

          1. 引言

          既然是在講 Tomcat ,那么一個(gè) HTTP 請(qǐng)求的請(qǐng)求流程是無論如何也繞不開的。

          首先拋開所有,使用我們現(xiàn)有的知識(shí)面,猜測(cè)一下一個(gè)請(qǐng)求被 Tomcat 處理的過程:

          1.?客戶端(瀏覽器)發(fā)送一個(gè)請(qǐng)求(HTTP)
          2.?建立?Socket?連接
          3.?通過?Socket?讀取數(shù)據(jù)
          4.?根據(jù)協(xié)議(HTTP)解析請(qǐng)求
          5.?調(diào)用對(duì)應(yīng)的代碼完成響應(yīng)

          上面這套流程,我相信任何一個(gè) Java 碼農(nóng)都能想得到,當(dāng) Tomcat 接受到請(qǐng)求后,經(jīng)過一系列的基礎(chǔ)處理,最終會(huì)調(diào)用到我們自己的業(yè)務(wù)程序上,或者說是 Servlet 上,在早期,這些請(qǐng)求會(huì)由我們自己實(shí)現(xiàn)的 jsp 或者是 Servlet 進(jìn)行接收,隨著時(shí)代的發(fā)展以及演進(jìn),出現(xiàn)了 Struts 和 Spring 等中間件來幫助我們完成基礎(chǔ)的請(qǐng)求處理,使得開發(fā)人員更加關(guān)注具體的業(yè)務(wù)。

          我想很多人都很好奇, Tomcat 是如何將這些 HTTP 請(qǐng)求轉(zhuǎn)交給我們的 Servlet 的?

          2. Connector 初始化

          上一篇我們?cè)诹?Tomcat 啟動(dòng)流程的時(shí)候,最后執(zhí)行初始化的是 org.apache.catalina.connector.Connector#initInternal() ,這時(shí)整個(gè)初始化流程到了 Connector ,看一下這段代碼:

          //?去除部分代碼
          protected?void?initInternal()?throws?LifecycleException?{
          ????super.initInternal();
          ????//?Initialize?adapter
          ????adapter?=?new?CoyoteAdapter(this);
          ????protocolHandler.setAdapter(adapter);
          ????//?......
          ????try?{
          ????????protocolHandler.init();
          ????}?catch?(Exception?e)?{
          ????????throw?new?LifecycleException(
          ????????????????sm.getString("coyoteConnector.protocolHandlerInitializationFailed"),?e);
          ????}
          }

          這段代碼中主要做了兩件事情:

          • 構(gòu)造了 CoyoteAdapter 對(duì)象,并且將其設(shè)置為 ProtocolHandler 的 Adapter 。
          • 調(diào)用了 org.apache.coyote.ProtocolHandler#init() 的方法。

          先說第二件事情,調(diào)用了 org.apache.coyote.ProtocolHandler#init()ProtocolHandler 是在構(gòu)造方法中進(jìn)行的初始化,這里的核心代碼是:

          setProtocol(protocol)

          再看下 setProtocol() 這個(gè)方法做了啥:

          public?void?setProtocol(String?protocol)?{
          ????boolean?aprConnector?=?AprLifecycleListener.isAprAvailable()?&&
          ????????????AprLifecycleListener.getUseAprConnector();
          ????if?("HTTP/1.1".equals(protocol)?||?protocol?==?null)?{
          ????????if?(aprConnector)?{
          ????????????setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
          ????????}?else?{
          ????????????setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
          ????????}
          ????}?else?if?("AJP/1.3".equals(protocol))?{
          ????????if?(aprConnector)?{
          ????????????setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
          ????????}?else?{
          ????????????setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
          ????????}
          ????}?else?{
          ????????setProtocolHandlerClassName(protocol);
          ????}
          }

          看到這里可以知道,主邏輯分成了兩塊,一塊是使用 HTTP/1.1 協(xié)議,另一塊是使用了 AJP/1.3 的協(xié)議,這里通過協(xié)議的不同,最終初始化了不同的類。

          如果是使用 HTTP/1.1 的協(xié)議,則采用了 org.apache.coyote.http11.Http11AprProtocol 或者 org.apache.coyote.http11.Http11NioProtocol,如果是采用 AJP/1.3 則采用 org.apache.coyote.ajp.AjpAprProtocol 或者是 org.apache.coyote.ajp.AjpNioProtocol

          看下 org.apache.coyote.http11.Http11AprProtocolorg.apache.coyote.ajp.AjpAprProtocol 繼承關(guān)系圖:

          可以看到這兩個(gè)類都繼承自 org.apache.coyote.AbstractProtocol ,通過查看 org.apache.coyote.AbstractProtocol#init() 方法,可以看到是調(diào)用了 org.apache.tomcat.util.net.AbstractEndpoint#init() ,而 AbstractEndpoint 的實(shí)例化操作是在實(shí)例化 AjpProtocolHttp11Protocol 的時(shí)候在其構(gòu)造函數(shù)中實(shí)例化的,而在 AjpProtocolHttp11Protocol 的構(gòu)造函數(shù)中,實(shí)際上是都初始化了 org.apache.tomcat.util.net.JIoEndpoint ,只不過根據(jù)不同的 HTTP 或者是 AJP 協(xié)議,它們具有不同的連接處理類。其中 Http11Protocol 的連接處理類為 org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler ,而連接處理類為 org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler

          除此之外, ProtocolHandler 還有其他實(shí)現(xiàn),都在 org.apache.coyote 這個(gè)包中,一些常見的類圖如下:

          因此到這里我們基本清楚了 Connector 的初始化流程,總結(jié)如下:

          //1?HTTP/1.1協(xié)議連接器
          org.apache.catalina.connector.Connector#init
          ->org.apache.coyote.http11.Http11AprProtocol#init
          -->org.apache.tomcat.util.net.AprEndpoint#init
          (org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler)

          //?2?AJP/1.3協(xié)議連接器
          org.apache.catalina.connector.Connector#init
          ->org.apache.coyote.ajp.AjpAprProtocol#init
          -->org.apache.tomcat.util.net.AprEndpoint#init
          (org.apache.coyote.ajp.AjpAprProtocol.AjpConnectionHandler)

          3. Connector 啟動(dòng)

          ProtocolHandler 的初始化稍微有些特殊,Server、Service、Connector 這三個(gè)容器的初始化順序?yàn)椋篠erver -> Service -> Connector 。值得注意的是, ProtocolHandler 作為 Connector 的子容器,其初始化過程并不是由 Connector 的 initInternal 方法調(diào)用的,而是與啟動(dòng)過程一道被 Connector 的 startInternal 方法所調(diào)用。

          @Override
          protected?void?startInternal()?throws?LifecycleException?{
          ????//?Validate?settings?before?starting
          ????if?(getPort()?0)?{
          ????????throw?new?LifecycleException(sm.getString(
          ????????????????"coyoteConnector.invalidPort",?Integer.valueOf(getPort())));
          ????}
          ????setState(LifecycleState.STARTING);
          ????try?{
          ????????protocolHandler.start();
          ????}?catch?(Exception?e)?{
          ????????throw?new?LifecycleException(
          ????????????????sm.getString("coyoteConnector.protocolHandlerStartFailed"),?e);
          ????}
          }

          這里也總共做了兩件事兒:

          • 將 Connector 容器的狀態(tài)更改為啟動(dòng)中(LifecycleState.STARTING) 。
          • 啟動(dòng) ProtocolHandler 。

          簡(jiǎn)單起見,以 Http11Protocol 為例介紹 ProtocolHandler 的 start 方法:

          由于 ProtocolHandler 是一個(gè)接口,它的 start 方法有兩個(gè)抽象類進(jìn)行實(shí)現(xiàn):

          • org.apache.coyote.AbstractAjpProtocol
          • org.apache.coyote.ajp.AbstractProtocol

          這里我們僅討論 org.apache.coyote.ajp.AbstractProtocol ,看下它的 start 的方法:

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

          ????endpoint.start();

          ????//?Start?timeout?thread
          ????asyncTimeout?=?new?AsyncTimeout();
          ????Thread?timeoutThread?=?new?Thread(asyncTimeout,?getNameInternal()?+?"-AsyncTimeout");
          ????int?priority?=?endpoint.getThreadPriority();
          ????if?(priority??Thread.MAX_PRIORITY)?{
          ????????priority?=?Thread.NORM_PRIORITY;
          ????}
          ????timeoutThread.setPriority(priority);
          ????timeoutThread.setDaemon(true);
          ????timeoutThread.start();
          }

          這里最核心的一句代碼是調(diào)用了 endpoint.start() ,這里的 endpoint 是抽象類 AbstractEndpoint#start() :

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

          這一段也做了兩件事兒:

          • 判斷當(dāng)前綁定狀態(tài),如果沒有綁定,則會(huì)先去綁定,調(diào)用 bind()
          • 然后調(diào)用 startInternal() 進(jìn)行初始化。

          AbstractEndpoint 有三個(gè)子類:

          我們專注于 AprEndpoint 這個(gè)子類,上面 AbstractEndpoint 抽象類中的兩個(gè)方法 bind()startInternal() 都會(huì)在這個(gè)類中進(jìn)行實(shí)現(xiàn)。

          我們先看 bind() 方法:

          /**
          ????*?Initialize?the?endpoint.
          ????*/

          @Override
          public?void?bind()?throws?Exception?{

          ????//?Create?the?root?APR?memory?pool
          ????try?{
          ????????rootPool?=?Pool.create(0);
          ????}?catch?(UnsatisfiedLinkError?e)?{
          ????????throw?new?Exception(sm.getString("endpoint.init.notavail"));
          ????}

          ????//?Create?the?pool?for?the?server?socket
          ????serverSockPool?=?Pool.create(rootPool);
          ????//?Create?the?APR?address?that?will?be?bound
          ????String?addressStr?=?null;
          ????if?(getAddress()?!=?null)?{
          ????????addressStr?=?getAddress().getHostAddress();
          ????}
          ????int?family?=?Socket.APR_INET;
          ????if?(Library.APR_HAVE_IPV6)?{
          ????????if?(addressStr?==?null)?{
          ????????????if?(!OS.IS_BSD)?{
          ????????????????family?=?Socket.APR_UNSPEC;
          ????????????}
          ????????}?else?if?(addressStr.indexOf(':')?>=?0)?{
          ????????????family?=?Socket.APR_UNSPEC;
          ????????}
          ????????}

          ????long?inetAddress?=?Address.info(addressStr,?family,
          ????????????getPort(),?0,?rootPool);
          ????//?Create?the?APR?server?socket
          ????serverSock?=?Socket.create(Address.getInfo(inetAddress).family,
          ????????????Socket.SOCK_STREAM,
          ????????????Socket.APR_PROTO_TCP,?rootPool);
          ????if?(OS.IS_UNIX)?{
          ????????Socket.optSet(serverSock,?Socket.APR_SO_REUSEADDR,?1);
          ????}
          ????if?(Library.APR_HAVE_IPV6)?{
          ????????if?(getIpv6v6only())?{
          ????????????Socket.optSet(serverSock,?Socket.APR_IPV6_V6ONLY,?1);
          ????????}?else?{
          ????????????Socket.optSet(serverSock,?Socket.APR_IPV6_V6ONLY,?0);
          ????????}
          ????}
          ????//?Deal?with?the?firewalls?that?tend?to?drop?the?inactive?sockets
          ????Socket.optSet(serverSock,?Socket.APR_SO_KEEPALIVE,?1);
          ????//?Bind?the?server?socket
          ????int?ret?=?Socket.bind(serverSock,?inetAddress);
          ????if?(ret?!=?0)?{
          ????????throw?new?Exception(sm.getString("endpoint.init.bind",?""?+?ret,?Error.strerror(ret)));
          ????}
          ????//?Start?listening?on?the?server?socket
          ????ret?=?Socket.listen(serverSock,?getAcceptCount());
          ????if?(ret?!=?0)?{
          ????????throw?new?Exception(sm.getString("endpoint.init.listen",?""?+?ret,?Error.strerror(ret)));
          ????}
          ????if?(OS.IS_WIN32?||?OS.IS_WIN64)?{
          ????????//?On?Windows?set?the?reuseaddr?flag?after?the?bind/listen
          ????????Socket.optSet(serverSock,?Socket.APR_SO_REUSEADDR,?1);
          ????}

          ????//?Enable?Sendfile?by?default?if?it?has?not?been?configured?but?usage?on
          ????//?systems?which?don't?support?it?cause?major?problems
          ????if?(!useSendFileSet)?{
          ????????setUseSendfileInternal(Library.APR_HAS_SENDFILE);
          ????}?else?if?(getUseSendfile()?&&?!Library.APR_HAS_SENDFILE)?{
          ????????setUseSendfileInternal(false);
          ????}

          ????//?Initialize?thread?count?default?for?acceptor
          ????if?(acceptorThreadCount?==?0)?{
          ????????//?FIXME:?Doesn't?seem?to?work?that?well?with?multiple?accept?threads
          ????????acceptorThreadCount?=?1;
          ????}

          ????//?Delay?accepting?of?new?connections?until?data?is?available
          ????//?Only?Linux?kernels?2.4?+?have?that?implemented
          ????//?on?other?platforms?this?call?is?noop?and?will?return?APR_ENOTIMPL.
          ????if?(deferAccept)?{
          ????????if?(Socket.optSet(serverSock,?Socket.APR_TCP_DEFER_ACCEPT,?1)?==?Status.APR_ENOTIMPL)?{
          ????????????deferAccept?=?false;
          ????????}
          ????}

          ????//?Initialize?SSL?if?needed
          ????if?(isSSLEnabled())?{
          ????????for?(SSLHostConfig?sslHostConfig?:?sslHostConfigs.values())?{
          ????????????createSSLContext(sslHostConfig);
          ????????}
          ????????SSLHostConfig?defaultSSLHostConfig?=?sslHostConfigs.get(getDefaultSSLHostConfigName());
          ????????if?(defaultSSLHostConfig?==?null)?{
          ????????????throw?new?IllegalArgumentException(sm.getString("endpoint.noSslHostConfig",
          ????????????????????getDefaultSSLHostConfigName(),?getName()));
          ????????}
          ????????Long?defaultSSLContext?=?defaultSSLHostConfig.getOpenSslContext();
          ????????sslContext?=?defaultSSLContext.longValue();
          ????????SSLContext.registerDefault(defaultSSLContext,?this);

          ????????//?For?now,?sendfile?is?not?supported?with?SSL
          ????????if?(getUseSendfile())?{
          ????????????setUseSendfileInternal(false);
          ????????????if?(useSendFileSet)?{
          ????????????????log.warn(sm.getString("endpoint.apr.noSendfileWithSSL"));
          ????????????}
          ????????}
          ????}
          }

          這個(gè)方法上面的注釋已經(jīng)寫的比較清楚了,首先第一個(gè)方法注釋就告訴我們這個(gè)方法是用來初始化 endpoint 的,大體做了這么幾件事兒:

          • 創(chuàng)建了 APR 的 rootPool ,從命名上看這應(yīng)該是一個(gè)根連接池。
          • 創(chuàng)建一個(gè) serverSockPool ,使用剛才創(chuàng)建的 rootPool 進(jìn)行創(chuàng)建,這個(gè)命名大家就都看得懂了。
          • 創(chuàng)建用來做綁定的 APR 的地址。
          • 創(chuàng)建一個(gè) APR server socket -> serverSock ,這里開啟了 socket 。
          • 將剛才創(chuàng)建的 server 和 socket 進(jìn)行綁定。
          • 開啟 server socket 上面的監(jiān)聽。
          • 一些系統(tǒng)層面的設(shè)置。
          • 如果需要的話,還會(huì)進(jìn)行一些 SSL 的相關(guān)設(shè)置。

          接著看下 startInternal() 方法:

          /**
          ??*?Start?the?APR?endpoint,?creating?acceptor,?poller?and?sendfile?threads.
          ??*/

          @Override
          public?void?startInternal()?throws?Exception?{

          ????if?(!running)?{
          ????????running?=?true;
          ????????paused?=?false;

          ????????processorCache?=?new?SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
          ????????????????socketProperties.getProcessorCache());

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

          ????????initializeConnectionLatch();

          ????????//?Start?poller?thread
          ????????poller?=?new?Poller();
          ????????poller.init();
          ????????Thread?pollerThread?=?new?Thread(poller,?getName()?+?"-Poller");
          ????????pollerThread.setPriority(threadPriority);
          ????????pollerThread.setDaemon(true);
          ????????pollerThread.start();

          ????????//?Start?sendfile?thread
          ????????if?(getUseSendfile())?{
          ????????????sendfile?=?new?Sendfile();
          ????????????sendfile.init();
          ????????????Thread?sendfileThread?=
          ????????????????????new?Thread(sendfile,?getName()?+?"-Sendfile");
          ????????????sendfileThread.setPriority(threadPriority);
          ????????????sendfileThread.setDaemon(true);
          ????????????sendfileThread.start();
          ????????}

          ????????startAcceptorThreads();
          ????}
          }

          這個(gè)方法所有的前提條件都在于如果 AprEndpoint 尚未出于運(yùn)行中,即 running == true ,首先如果沒有創(chuàng)建線程池 getExecutor() == null ,則需要調(diào)用 createExecutor() 方法創(chuàng)建線程池和任務(wù)隊(duì)列 TaskQueue

          public?void?createExecutor()?{
          ????internalExecutor?=?true;
          ????TaskQueue?taskqueue?=?new?TaskQueue();
          ????TaskThreadFactory?tf?=?new?TaskThreadFactory(getName()?+?"-exec-",?daemon,?getThreadPriority());
          ????executor?=?new?ThreadPoolExecutor(getMinSpareThreads(),?getMaxThreads(),?60,?TimeUnit.SECONDS,taskqueue,?tf);
          ????taskqueue.setParent(?(ThreadPoolExecutor)?executor);
          }

          剩下兩個(gè)是創(chuàng)建了兩個(gè)線程,分別是 poller 和 sendfile ,這兩個(gè)都是 AprEndpoint 的內(nèi)部類,這兩個(gè)線程一個(gè)是用來做輪詢,另一個(gè)是用來做數(shù)據(jù)發(fā)送。

          至此, Tomcat 中為請(qǐng)求處理的準(zhǔn)備工作已經(jīng)完成。





          感謝閱讀



          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美精品久久久 | 影音先锋AV男人资源站 | 国产黄色电影一区 | 亚洲AV成人无码网天堂 | aaa国产精品 |