學習Tomcat(三)之容器連接器

Tomcat最底層使用的是Java標準的SocketServer和Socket接受和處理請求,但是Socket接受到的數據是網絡運輸層的TCP或UDP協議的數據,需要轉為Http或者其它應用層協議的數據。Tomcat中就是通過連接器Connector來管理Socket連接、解析Scoket請求為Request並封裝響應數據為Response對象。新版本的Tomcat容器使用的連接器是Coyote框架,本文會詳細介紹Connector的Coyote框架原理。本文很多內容參考了這篇博客

什麼是Coyote

Coyote是Tomcat鏈接器框架的名稱,是Tomcat服務器提供的供客戶端訪問的外部接口。客戶端通過Coyote與服務器建立鏈接、發送請求並且接收響應。

Coyote封裝了底層的網絡通信(Socket請求以及響應處理),為Catalina容器提供了統一的接口,是Catalina容器與具體的請求協議以及 I/O方式解耦。Coyote將Socket輸入轉換為Request對象,交由Catalina容器進行處理,處理請求完成之後,Catalina通過Coyote提供的Response對象將結果寫入輸出流。

Coyote作為獨立的模塊,只負責具體協議和I/O的處理,與Servlet規範實現沒有直接關係,因此即便是Request和Response對象也並未實現Servlet規範對應的接口,而是在Catalina中將他們進一步封裝為ServletRequest何ServletResponse。

連接器Coyote功能圖

Coyote支持的協議

Coyote框架支持以下三種應用層協議:

  1. HTTP/1.1協議:這是絕大多數Web應用採用的訪問協議,主要用於Tomcat單獨運行(不予Web服務器集成)的情況。
  2. AJP協議:用於和Web服務器(如Apache HTTP Server)集成,以實現針對靜態資源的優化以及集群部署,當前支持AJP/1.3。
  3. HTTP/2.0協議:下一代HTTP協議,自Tomcat8.5以及9.0版本開始支持,截止目前,主流的最新版本均已支持HTTP/2.0。

針對HTTP和AJP協議,Coyote又按照 I/O方式分別提供了不同的選擇方案(自8.5/9.0版本起,Tomcat已出了對BIO的支持)。

  1. NIO:採用Java NIO類庫實現。
  2. NIO2:採用JDK 7最新的NIO2類庫實現。
  3. APR:採用APR(Apache可移植運行庫)實現

在8.0之前,Tomcat默認採用的I/O方式為BIO,之後採用NIO。無論NIO、NIO2還是APR,在性能方面均優於以往的BIO。如果採用APR,甚至可以達到接近於 Apache HTTP Server的響應性能。

在Coyote中,HTTP/2.0的處理方式與HTTP/1.1和AJP不同,採用一種升級協議的方式實現,這也是有HTTP/2.0的傳輸方案決定的。

可以採用一種簡單的分層視圖來描述Tomcat對協議以及 I/O方式的支持,如下圖所示:

Tomcat IO方式

Connector的核心概念

在Connector中有如下幾個核心概念:

  • Endpoint:Coyote通信端點,即通信監聽的接口,是具體的Socker接收處理類,是對傳輸層的抽象。Tomcat並沒有Endpoint接口,而是提供了一個抽象類AvstractEndpoint。根據I/O方式的不同,提供了NioEndpoint(NIO)、AprEndpoint(APR)以及Nio2Endpoint(NIO2)三個實現。
  • Processor:Coyote協議處理接口,負責構造Request和Response對象,並且通過Adapter將其提交到Catalina容器處理,是對應用層的抽象。Processor是單線程的,Tomcat在同一次鏈接中復用Processor。Tomcat按照協議的不同提供了3個實現類:Http11Processor(HTTP/1.1)、AjpProcessor(AJP)、StreamProcessor(HTTP/2.0)。除此之外,他還提供了兩個用於處理內部處理的實現:UpgradeProcessorInternal和UpgradeProcessorExternal,前者用於處理內部支持的升級協議(如HTTP/2.0和WebSocket),後者用於處理外部擴展的升級協議支持。
  • UpgradeProtocol:Tomcat採用UpgradeProtocol接口表示HTTP升級協議,當前只提供了一個實現(Http2Protocol)用於處理HTTP/2.0.他根據請求創建一個用於升級處理的令牌UpgradeToken,該令牌中包含了具體的HTTP升級處理器HttpUpgradeHandler,HTTP/2.0的處理器實現為Http2UpgradeHandler。Tomcat中的WebSocket也是通過UpgradeToken機制實現的。

Connector請求處理流程

連接器Connector結構圖

Connector處理請求的流程如上所示,我們一步步分析一下流程的內容:

  1. 當Connector啟動時,會同時啟動其持有的Endpoint實例。Endpoint並允許多個線程(有屬性acceptorThreadCount確定),每個線程允許一個AbstractEndpoint.Acceptor實例。在AbstractEndpoint.Acceptor實例中監聽端口通信(I/O方式不同,具體的處理方式也不同),而且只要Endpoint處於運行狀態,始終循環監聽。
  2. 當監聽到請求時,Acceptor將Socker封裝為SocketWrapper實例(此時並未讀取數據),並交由一個SocketProcessor對象處理(此過程也由線程池異步處理)。此部分根據I/O方式的不同處理會有所不同,如NIO採用輪詢的方式檢測SelectionKey是否就緒。如果就緒,則獲取一個有效的SocketProcessor對象並且提交線程池處理。
  3. SocketProcessor是一個線程池Worker實例,每一個I/O方式均有自己的實現。他首先判斷Socket的狀態(如完成SSL握手),然後提交到ConnectionHandler處理。
  4. ConnectionHandler是AbstractProtocol的一個內部類,主要用於鏈接選擇一個合適的Processor實現以進行請求處理。
    • 提升性能,他針對每個有效的理解都會緩存器Processor對象。不僅如此,當前鏈接關閉時,其Processor對象還會被釋放到一個回收隊列(升級協議不會回收),這樣後續鏈接可以重置並且重複利用,以減少對象構造。
    • 在處理請求時,他首先會從緩存中獲取當前鏈接的Processor對象。如果不存在,則嘗試根據協商協議構造Processor(如HTTP/2.0請求)。如果不存在協商協議(如HTTP/1.1請求),則從回收隊列中獲取一個已釋放的Processor對象使用。如果回收隊列中沒有可用的對象,那麼由具體的協議創建一個Processor使用(同時註冊到緩存)。
  5. 協議升級時,ConnectionHandler會從當前Processor得到一個UpgradeToken對象(如果沒有,則默認為HTTP/2),並構造一個升級Processor實例(如果是Tomcat支持的協議則會是UpgradeProcessorInternal,否則是UpgradeProcessorExternal)替換當前的Processor,並將當前的Processor釋放回收。替換後,該鏈接的後續處理將由升級Process完成。
  6. 通過UpgradeToken中HttpUpgradehandler對象的init()方法進行初始化,以便準備開始啟用新協議。

我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd

qrcode_for_gh_83670e17bbd7_344-2021-09-04-10-55-16

本文最先發佈至微信公眾號,版權所有,禁止轉載!

Tags: