日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入理解 Tomcat(八)源码剖析之连接器

發布時間:2024/4/14 编程问答 71 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解 Tomcat(八)源码剖析之连接器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這是我們分析tomcat的第八篇文章,這次我們分析連接器,我們早就想分析連接器了,因為各種原因拖了好久。不過也確實復雜。

首先我們之前定義過連接器:

Tomcat都是在容器里面處理問題的, 而容器又到哪里去取得輸入信息呢? Connector就是專干這個的。 他會把從socket傳遞過來的數據, 封裝成Request, 傳遞給容器來處理。 通常我們會用到兩種Connector,一種叫http connectoer, 用來傳遞http需求的。 另一種叫AJP, 在我們整合apache與tomcat工作的時候,apache與tomcat之間就是通過這個協議來互動的。 (說到apache與tomcat的整合工作, 通常我們的目的是為了讓apache 獲取靜態資源, 而讓tomcat來解析動態的jsp或者servlet。)

簡單來說,連接器就是接收http請求并解析http請求,然后將請求交給servlet容器。

那么在 Tomcat中 ,那個類表示連接器呢? 答案是 org.apache.catalina.connector.Connector,該類繼承自 LifecycleMBeanBase, 也就是說,該類的生命周期歸屬于容器管理。而該類的父容器是誰呢? 答案是 org.apache.catalina.core.StandardService,也就是我們的Service 組件,StandardService是該接口的標準實現。StandardService 聚合了 Connector 數組和一個Container 容器,也就驗證了我們之前說的一個Service 組件中包含一個Container和多個連接器。

那么連接器什么時候初始化被放入容器和JMX呢?這是個大問題,也是我們今天的主要問題。

1. Tomcat 解析 server.xml 并創建對象

我們之前扒過啟動源碼,我們知道,在Catalina 的 load 方法中是初始化容器的方法,所有的容器都是在該方法中初始化的。Connector 也不例外。我們還記得 Tomcat 的conf 目錄下的server.xml 文件嗎?

<?xml version='1.0' encoding='utf-8'?><Server port="8005" shutdown="SHUTDOWN"><Listener className="org.apache.catalina.startup.VersionLoggerListener" /><Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /><Listener className="org.apache.catalina.core.JasperListener" /><Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /><Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /><Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /><GlobalNamingResources><Resource name="UserDatabase" auth="Container"type="org.apache.catalina.UserDatabase"description="User database that can be updated and saved"factory="org.apache.catalina.users.MemoryUserDatabaseFactory"pathname="conf/tomcat-users.xml" /></GlobalNamingResources><Service name="Catalina"><Connector port="8061" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" /><Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /><Engine name="Catalina" defaultHost="localhost"><Realm className="org.apache.catalina.realm.LockOutRealm"><Realm className="org.apache.catalina.realm.UserDatabaseRealm"resourceName="UserDatabase"/></Realm><Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="true"><Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"prefix="localhost_access_log." suffix=".txt"pattern="%h %l %u %t &quot;%r&quot; %s %b" /></Host></Engine></Service> </Server> 復制代碼

可以看到該配置文件中有2個Connector 標簽,有就是說默認有2個連接器。一個是HTTP協議,一個AJP協議。

我們的Connector是什么時候創建的呢?就是在解析這個xml文件的時候,那么是怎么解析的呢?我們平時在解析 xml 的時候經常使用dom4j(真的不喜歡xml,最愛json),而tomcat 使用的是 Digester 解析xml,我們來看看 Catalina.load() 關于解析并創建容器的關鍵代碼:

public void load() {// Create and execute our DigesterDigester digester = createStartDigester();digester.parse(inputSource);} 復制代碼

首先創建一個 Digester, 其中的關鍵代碼我們看看:

Digester digester = new Digester();digester.addRule("Server/Service/Connector",new ConnectorCreateRule()); digester.addRule("Server/Service/Connector",new SetAllPropertiesRule(new String[]{"executor"})); digester.addSetNext("Server/Service/Connector","addConnector","org.apache.catalina.connector.Connector"); 復制代碼

上面的代碼的意思是將對應的字符串創建成對應的角色,以便后面和xml對應便解析。

我們再看看它是如何解析的,由于 digester.parse(inputSource) 這個方法調用層次太深,而且該方法只是解析xml,因此樓主把就不把每段代碼貼出來了,我們看看IDEA生成的該方法的方法調用棧:這些方法都是在 rt.jar 包中,因此我們不做分析了。主要就是解析xml。

上圖是樓主在 Digester.startDocument 的方法中打的斷點。該方法作用為開始解析 xml 做準備。

上圖是樓主在 Digester.startElement 的方法中打的斷點,startDocument 和 startElement 是多次交替執行的,確定他們執行邏輯的是什么方法呢?從堆棧圖中我們可以看到:是 com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse()這個方法,代碼我就不貼出來了,很長沒有意義,該方法在 819行間接調用 startDocument,在841行間接調用startElement。上下執行,并且回執行多次。因為 xml 會解析多次嘛。

我們重點說說 startElement 方法:

public void startElement(String namespaceURI, String localName,String qName, Attributes list) {List<Rule> rules = getRules().match(namespaceURI, match);matches.push(rules);if ((rules != null) && (rules.size() > 0)) {for (int i = 0; i < rules.size(); i++) {try {Rule rule = rules.get(i);if (debug) {log.debug(" Fire begin() for " + rule);}rule.begin(namespaceURI, name, list);} catch (Exception e) {log.error("Begin event threw exception", e);throw createSAXException(e);} catch (Error e) {log.error("Begin event threw error", e);throw e;}}} } 復制代碼

樓主只貼了關鍵代碼,就是for循環中的邏輯,便利 Rule 集合,Rule 是什么呢?是我們之前 createStartDigester 方法里創建的。而 Rule 是一個接口,tomcat 中有很多不同的實現。然后循環調用他們的 begin 方法。我們看看有哪些實現:

我們從上圖中看到了有很多的實現,而我們今天只關注連接器:也就是 ConnectorCreateRule 的 begin 方法:

@Overridepublic void begin(String namespace, String name, Attributes attributes)throws Exception {Service svc = (Service)digester.peek();Executor ex = null;if ( attributes.getValue("executor")!=null ) {ex = svc.getExecutor(attributes.getValue("executor"));}Connector con = new Connector(attributes.getValue("protocol"));if ( ex != null ) _setExecutor(con,ex);digester.push(con);}復制代碼

方法不長,我們看看該方法邏輯,該方法首先從List中取出一個Service,然后會分別創建2個連接器,一個是HTTP, 一個是AJP,也就是我們配置文件中寫的。

現在,我們已經剖析了tomcat 是如何解析xml的,并如何創建對象的,接下來,我們就看看創建對象的邏輯。

2. 創建連接器對象

我們來到我們的Connector類的構造方法:

public Connector() {this(null);}public Connector(String protocol) {setProtocol(protocol);// Instantiate protocol handlertry {Class<?> clazz = Class.forName(protocolHandlerClassName);this.protocolHandler = (ProtocolHandler) clazz.newInstance();// 反射創建protocolHandler 默認 http1.1 協議實現 (org.apache.coyote.http11.Http11Protocol)} catch (Exception e) {log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);}}復制代碼

我記得阿里規約里說,構造器不要太復雜,復雜的邏輯請放在init里,不知道tomcat這么寫算好還是不好呢?嘿嘿。我們還是來看看我們的邏輯吧。

  • 根據傳進來的字符串設置協議處理器類名(setProtocol 方法中調用了setProtocolHandlerClassName 方法)。
  • 根據剛剛設置好的 protocolHandlerClassName 反射創建 ProtocolHandler 類型的對象。
  • 既然是反射創建,那么我們就要看看完整的類名是什么了,所以需要看看設置 protocolHandlerClassName 方法的細節:

    public void setProtocol(String protocol) {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 {if ("HTTP/1.1".equals(protocol)) {setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");} else if ("AJP/1.3".equals(protocol)) {setProtocolHandlerClassName("org.apache.coyote.ajp.AjpProtocol");} else if (protocol != null) {setProtocolHandlerClassName(protocol);}}}復制代碼

    此方法會直接進入下面的else塊,我們知道,該處可能會傳 HTTP 或者 AJP ,根據不同的協議創建不同的協議處理器。也就是連接器,我們看到這里的全限定名是 org.apache.coyote.http11.Http11Protocol 或者 org.apache.coyote.ajp.AjpProtocol,這兩個類都是在 coyote 包下,也就是連接器模塊。

    好了,到現在,我們的 Connector 對象就創建完畢了,創建它的過程同時也根據配置文件創建了 protocolHandler, 他倆是依賴關系。

    3. Http11Protocol 協議處理器構造過程

    創建了 Http11Protocol 對象,我們有必要看看他的構造過程是什么樣的。按照tomcat的性格,一般構造器都很復雜,所以,我們找到該類,看看他的類和構造器:

    該類的類說明是這樣說的:

    抽象協議的實現,包括線程等。處理器是單線程的,特定于基于流的協議,不適合像JNI那樣的Jk協議。

    我們看看構造方法

    public Http11Protocol() {endpoint = new JIoEndpoint();cHandler = new Http11ConnectionHandler(this);((JIoEndpoint) endpoint).setHandler(cHandler);setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);}復制代碼

    我就說嘛,肯定和復雜。復雜也要看啊。

  • 創建以了一個 JIoEndpoint 對象。
  • 創建了一個 Http11ConnectionHandler 對象,參數是 Http11Protocol;
  • 設置處理器為 Http11ConnectionHandler 對象。
  • 設置一些屬性,比如超時,優化tcp性能。
  • 那么我們來看看 JIoEndpoint 這個類,這個類是什么玩意,如果大家平時調試tomcat比較多的話,肯定會熟悉這個類,樓主今天就遇到了,請看:

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:748) 復制代碼

    異常信息,我們看倒數第四行,就是 JIoEndpoint 的內部類 SocketProcessor 的 run 方法報錯了,我們今天就親密接觸一下這個類,順便扒了它的衣服:

    赤裸裸的在我們面前。所有錯誤的根源都在該方法中。

    不扯了,我們繼續看 JIoEndpoint 的構造器,該構造器很簡單,就是設置最大連接數。默認是0,我們看代碼:

    上圖中什么看到該方法將 maxConnections 設置為0,本來是10000,然后進入else if(maxCon > 0) 的邏輯。這里也就完成了 JIoEndpoint 對象的創建過程。

    我們回到 Http11Protocol 的構造方法中,執行完了 JIoEndpoint 的創建過程,下面就執行 Http11ConnectionHandler 的構造。參數是Http11Protocol自己,Http11ConnectionHandler 是 Http11Protocol 的靜態內部類,該類中有一個屬性就是Http11Protocol,一個簡單的創建過程,然后設置 Http11Protocol 的 Handler 屬性為 Http11ConnectionHandler。可以感覺的到,Http11Protocol, JIoEndpoint , Http11ConnectionHandler 這三個類是互相依賴關系。

    至此,完成了 Http11Protocol 對象的創建。同時也完成了 Connector 對象的創建。 創建完對象干嘛呢。。。。不要想歪了,不是啪啪啪,而是初始化。

    4. Connector 連接器的初始化 init 方法

    我們知道 Connector 的父容器是 Service ,Service 執行 initInternal 方法初始化的時候會同時初始化子容器,也就是 Connector,在一個 for 循環重啟動。

    該段代碼抽取自 StandardService.initInternal 方法,也就是Service 組件。通過debug我們知道了該連接器數組中只有2個連接器,就是我們的HTTP和AJP,剛剛創建的。并調用他們的 init 方法。我們看看該方法,該方法同所有容器一樣,執行了LifecycleBase 的模板方法,重點在子類重寫的抽象方法 initInternal 中。

    這既是 Connector 的 initInternal 方法實現,該方法有幾個步驟:

  • 調用父類的 initInternal 方法,將自己注冊到JMX中。
  • 創建一個 CoyoteAdapter 對象,參數是自己。
  • 設置 Http11Protocol 的適配器為剛剛創建的 CoyoteAdapter 適配器。
  • 設置解析請求的請求方法類型,默認是 POST。
  • 初始化 Http11Protocol(不要小看這個類,Connector就是一個虛的,真正做事的就是這個類和 JIoEndpoint);
  • 初始化 mapperListener;
  • 我們重點關注 CoyoteAdapter 和 Http11Protocol 的初始化,CoyoteAdapter 是連接器的一種適配,構造參數是 Connector ,很明顯,他是要適配 Connector,這里的設計模式就是適配器模式了,所以,寫設計模式的時候,一定要在類名上加上設計模式的名字。方便后來人讀代碼。接下就是設置 Http11Protocol 的適配器為 剛剛構造的 CoyoteAdapter ,也就是說,tomcat 的設計者為了解耦或者什么將 Http11Protocol 和 Connector 中間插入了一個適配器。最后來到我們的 Http11Protocol 的初始化。

    這個 Http11Protocol 的初始化很重要。Http11Protocol 不屬于 Lifecycle 管理,他的 init 方法在他的抽象父類 org.apache.coyote.AbstractProtocol 中就已經寫好了,我們來看看該方法的實現(很重要):

    上圖就是 AbstractProtocol 的 init 方法,我們看看紅框中的邏輯。

  • 將 endpoint 注冊到JMX中。
  • 將 Http11ConnectionHandler(Http11Protocol 的 內部類)注冊到JMX中。
  • 設置 endpoint 的名字,Http 連接器是 http-bio-8080;
  • endpoint 初始化。
  • 設置JMX的邏輯我們就不講了,之前講生命周期的時候講過了,設置名字也沒生命好講的。最后講最重要的 endpoint 的初始化。我們來看看他的 init 方法。該方法是 JIoEndpoint 抽象父類 AbstractEndpoint 的模板方法。該類被3個類繼承:AprEndpoint, JIoEndpoint, NioEndpoint,我們今天只關心JIoEndpoint。我們還是先看看 AbstractEndpoint 的 init 方法吧:

    其中 bind()是抽象方法,然后設置狀態為綁定已經初始化。我們看看 JIoEndpoint 的 bind 方法。有興趣也可以看看其他 Endpoint 的 bind 方法,比如NIO。我們看看JIo的:

    這個方法很重要,我們仔細看看邏輯:

  • 設置最大線程數,默認是200;
  • 創建一個默認的 serverSocketFactory 工廠(就是一個封裝了ServerSocket 的類);
  • 使用剛剛工廠創建一個 serverSocket。因此,JIoEndpoint 也就有了 serverSocket。
  • 至此,我們完成了 Connector, Http11Protocol,JIoEndpoint 的初始化。

    接下來就是啟動了

    5. 連接器啟動

    如我們所知,Connector 啟動肯定在 startInternal 方法中,因此我們直接看此方法。

    該方法步驟如下:

  • 設置啟動中狀態。狀態更新會觸發事件監聽機制。
  • 啟動 org.apache.coyote.http11.Http11Protocol 的 srart 方法。
  • 啟動 org.apache.catalina.connector.MapperListener 的 start 方法。
  • 我們感興趣的是 org.apache.coyote.http11.Http11Protocol 的 srart 方法。該方法由其抽象父類 AbstractProtocol.start 執行,我們看看該方法:

    該方法主要邏輯是啟動 endpoint 的 start 方法。說明干事的還是 endpoint 啊 ,我們看看該方法實現,該方法調用了抽象父類的模板方法 AbstractEndpoint.start:

    其主要邏輯是調用子類重寫的 startInternal 方法,我們來看 JIoEndpoint 的實現:

    該方法可以說是 Tomcat 中 真正做事情的方法,絕對不是摸魚員工。說說他的邏輯:

  • 創建一個線程阻塞隊列,和一個線程池。
  • 初始化最大連接數,默認200.
  • 調用抽象父類 AbstractEndpoint 的 startAcceptorThreads 方法,默認創建一個守護線程。他的任務是等待客戶端請求,并將請求(socket 交給線程池)。AbstractEndpoint 中有一個 Acceptor 數組,作用接收新的連接和傳遞請求。
  • 創建一個管理超時socket 的線程。
  • 讓我們看看他的詳細實現:

    6. JIoEndpoint startInternal(Tomcat socket 管理) 方法的詳細實現

    先看第一步:創建一個線程阻塞隊列,和一個線程池。我們進入該方法:

    該方法步驟:

  • 創建一個 “任務隊列”,實際上是一個繼承了 LinkedBlockingQueue 的類。該隊列最大長度為 int 最大值 0x7fffffff。
  • 創建一個線程工廠,TaskThreadFactory 是一個繼承 ThreadFactory 的類,默認創建最小線程 10, 最大線程200, 名字為 “http-bio-8080-exec-” 拼接線程池編號,優先級為5。
  • 使用上面的線程工廠創建一個線程池,預先創建10個線程,最大線程200,線程空閑實際60秒.
  • 將線程池設置為隊列的屬性,方便后期判斷線程池狀態而做一些操作。
  • 再看第二步:初始化最大連接數,默認200.

    該方法很簡單,就是設置最大連接數為200;

    第三步:用抽象父類 AbstractEndpoint 的 startAcceptorThreads 方法,默認創建一個守護線程。他的任務是等待客戶端請求,并將請求(socket 交給線程池)。AbstractEndpoint 中有一個 Acceptor 數組,作用接收新的連接和傳遞請求。我們看看該方法:

    步驟:

  • 獲取可接收的連接數,并創建一個連接線程數組。
  • 循環該數組,設置優先級為5,設置為守護線程。 3.啟動該線程。
  • 該方法也不是很復雜,獲取的這個連接數,在完美初始化的時候,調用bind 方法的時候設置的,請看:

    設置為1.

    復雜的是 Acceptor 中的邏輯,Acceptor 是一個抽象靜態內部類,實現了 Runnable 接口,JIoEndpoint 類中也繼承了該類,其中 run 方法如下(高能預警,方法很長)。

    @Overridepublic void run() {int errorDelay = 0;// Loop until we receive a shutdown commandwhile (running) {// Loop if endpoint is pausedwhile (paused && running) {state = AcceptorState.PAUSED;try {Thread.sleep(50);} catch (InterruptedException e) {// Ignore}}if (!running) {break;}state = AcceptorState.RUNNING;try {//if we have reached max connections, waitcountUpOrAwaitConnection();Socket socket = null;try {// Accept the next incoming connection from the server// socketsocket = serverSocketFactory.acceptSocket(serverSocket);} catch (IOException ioe) {countDownConnection();// Introduce delay if necessaryerrorDelay = handleExceptionWithDelay(errorDelay);// re-throwthrow ioe;}// Successful accept, reset the error delayerrorDelay = 0;// Configure the socketif (running && !paused && setSocketOptions(socket)) {// Hand this socket off to an appropriate processorif (!processSocket(socket)) {countDownConnection();// Close socket right awaycloseSocket(socket);}} else {countDownConnection();// Close socket right awaycloseSocket(socket);}} catch (IOException x) {if (running) {log.error(sm.getString("endpoint.accept.fail"), x);}} catch (NullPointerException npe) {if (running) {log.error(sm.getString("endpoint.accept.fail"), npe);}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error(sm.getString("endpoint.accept.fail"), t);}}state = AcceptorState.ENDED;} 復制代碼

    其實邏輯也還好,不是那么復雜,我們化整為零,一個一個分析,首先判斷狀態,進入循環,然后設置一些狀態,最后進入一個 try 塊。

  • 執行 countUpOrAwaitConnection 方法,該方法注釋說:如果已經達到最大連接,就等待。
  • 阻塞獲取socket。
  • setSocketOptions(socket) 設置 tcp 一些屬性優化性能,比如緩沖字符大小,超時等。
  • 執行 processSocket(socket) 方法,將請求包裝一下交給線程池執行。
  • 我們看看第一個方法 countUpOrAwaitConnection:

    主要是 latch.countUpOrAwait() 這個方法,我們看看該方法內部實現:

    這個 Sync 類 變量是 繼承了java.util.concurrent.locks.AbstractQueuedSynchronizer 抽象類(該類是JDK 1.8 新增的),說實話,樓主不熟悉這個類。再一個今天的主題也不是并發,因此放過這個類,給大家一個鏈接 深度解析Java 8:AbstractQueuedSynchronizer的實現分析(下); 我們暫時知道這個方法作用是什么就行了,就像注釋說的:如果已經達到最大連接,就等待。我們繼續我們的分析。

    我們跳過設置 tcp 優化,重點查看 processSocket 方法,這個方法是 JIoEndpoint 的,我們看看該方法實現:

    該方法邏輯是:

  • 封裝一個 SocketWrapper。
  • 設置長連接時間為100, 3.然后封裝成 SocketProcessor(還記得這個類嗎,就是我們剛開始異常信息里出現的類,原來是在這里報錯的,哈哈) 交給線程池執行。
  • 到這里,我們必須停下來,因為如果繼續追蹤 SocketProcessor 這個類,這篇文章就停不下來了,樓主想留在下一篇文章慢慢咀嚼。慢慢品味。

    第四步:好了,回到我們的 JIoEndpoint.startInternal 方法,我們已經解析完了 startAcceptorThreads 方法,那么我們繼續向下走,看到一個 timeoutThread 線程。創建一個管理超時socket 的線程。設置為了守護線程,名字叫 “http-bio-8080-AsyncTimeout”,優先級為5.

    我們看看該程序的實現,還好,代碼不多:

    我們看看主要邏輯:

  • 獲取當前時間;
  • waitingRequests 的類型是 ConcurrentLinkedQueue<SocketWrapper>,一個并發安全的阻塞對壘,里面有包裝過的 SocketWrapper。
  • 判斷如果該隊列中有,則取出,判斷如果該socket 設定的超時時間大于0(默認-1),且當前時間大于訪問時間,則交給線程池處理。
  • 那么什么時候會往該 waitingRequests 里添加呢?我們看過之前的 SocketProcessor. run 方法, 如果 SocketState 的狀態是LONG,就設置該 socket 的訪問時間為當前時間,并添加進超時隊列。而這個超時的判斷也非常的復雜,想想也對,任何一個連接都不能隨意丟棄。所以需要更嚴謹的對待,萬一是個支付請求呢?

    好了,JIoEndpoint 的 startInternal 方法已經執行完畢,總結一下該方法:創建線程池,初始化最大連接數,啟動接收請求線程,設置超時線程??梢哉ftomcat 考慮的很周到。很完美。不過還有更完美的NIO還沒有體驗。

    7. 總結

    今天的文章真是超長啊,誰叫連接器是tomcat中最重要的組件呢?其實還沒有講完呢;我們講了連接器的如何根據server.xml 創建對象,如何初始化connector 和 endpoint ,如何啟動connector,如何啟動 endpoint中各個線程。樓主能力有限,暫時先講這么多,還有很多的東西我們都還沒講,沒事,留著下次講。

    天色已晚,樓主該回家了。各位再見!!!!

    good luck !!!!

    總結

    以上是生活随笔為你收集整理的深入理解 Tomcat(八)源码剖析之连接器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。