天天看點

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

首發CSDN:徐同學呀,原創不易,轉載請注明源連結。我是徐同學,用心輸出高品質文章,希望對你有所幫助。

本篇基于Tomcat10.0.6。

文章目錄

    • 一、前言
    • 二、Connector基本結構
    • 三、ProtocolHandler
      • 1、Endpoint通信端點
      • 2、Processor應用層協定解析
        • (1)ConnectionHandler建立合适的Processor
        • (2)協定更新
      • 3、配置方式
    • 四、Adapter
    • 五、請求處理完整流程
    • 六、要點回顧
    • 七、參考文獻

一、前言

Tomcat是一個非常優秀的開源項目,深深被它匠心獨具的架構理念所折服。技術的思維是互通的,哪有那麼多的高深莫測,都是對現實世界的抽象。

Tomcat作為Web應用伺服器,接收請求和處理請求是它的兩個核心功能。接收請求這步需要考慮适配多種網絡協定,主流的是

HTTP

協定,還有

WebSocket

協定、

AJP

協定等;處理請求就是找到對應的

Servlet

,執行業務代碼。

為了解耦和擴充性,Tomcat 設計了兩個核心元件來做這兩件事情:

  • 連接配接器(

    Connector

    )負責對外交流,建立

    Socket

    連接配接,讀取并解析網絡位元組流,生成

    Request

    Response

    對象并轉發給容器。
  • 容器(

    Container

    )負責内部處理,加載和管理

    Servlet

    ,通過

    Response

    對象的輸出流寫入響應結果。
    Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

二、Connector基本結構

具體來看看

Connector

有哪些功能:

  • 監聽網絡端口。
  • 接受網絡連接配接請求。
  • 讀取網絡請求位元組流。
  • 根據具體應用層協定(

    HTTP/AJP

    )解析位元組流,生成統一的

    Request

    Response

    對象。(需要注意,

    Connector

    為了相容多種協定,沒有耦合

    Servlet

    标準,是以這裡生成的

    Request

    Response

    對象沒有實作

    HttpServletRequest

    HttpServletResponse

    接口)。
  • Request

    Response

    對象轉換為

    HttpServletRequest

    HttpServletResponse

    對象。
  • HttpServletRequest

    HttpServletResponse

    對象轉發給

    Container

總結就是做了三件事:

  • 網絡通信。
  • 應用層協定解析和封裝。
  • Request

    Response

    對象轉換和傳遞。

對應

Connector

中的三個元件就是:

Endpoint

Processor

Adapter

,其中

Endpoint

Processor

放在一起抽象成了

ProtocolHandler

協定元件。

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

三、ProtocolHandler

ProtocolHandler

負責傳輸層網絡連接配接和應用層協定解析,由兩個核心部件

Endpoint

Processor

具體做這兩件事。

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

從實作類來看,Tomcat的連接配接器主要支援兩種協定:

HTTP/1.1

協定和

AJP

協定,實則還支援

HTTP/2.0

協定,

HTTP/2.0

的處理與

HTTP/1.1

AJP

不同,采用一種更新協定的方式實作。

  • HTTP/1.1

    協定:大家最熟悉的協定了,絕大多數web應用采用的通路協定,Tomcat既是

    Servlet

    容器又是HTTP伺服器,單獨運作就可以對外提供服務,但是一般會搭配

    Nginx

    web伺服器做反向代理和負載均衡。
  • HTTP/2.0

    協定:自Tomcat8.5開始支援,相較于

    HTTP/1.1

    采用二進制傳輸資料而非文本格式;對消息頭采用

    HPACK

    壓縮,提升傳輸效率;基于幀和流的多路複用,真正實作了基于一個連接配接多請求并發處理;支援伺服器主動推送。
  • AJP/1.3

    協定:全名Apache JServ Protocol,是Alexei Kosut建立的定向包通信協定,采用二進制格式傳輸可讀文本。用于內建Web伺服器,以提升靜态資源的通路性能,目前最新版本為1.3。目前

    Apache

    Tomcat

    Nginx

    Jetty

    JBoss

    等均已支援

    AJP

    。(名不見經傳,不過多解釋)

除了支援3種協定外,還分别支援3種I/O方式:

NIO

NIO2

APR

,Tomcat8.5之前預設還支援

BIO

,後來因為性能問題直接給删除了,APR也貼上了過期标簽。協定和I/O方式兩兩組合就出現了很多實作類:

Http11NioProtocol

Http11Nio2Protocol

Http11AprProtocol

(已過期)、

AjpNioProtocol

AjpNio2Protocol

AjpAjpProtocol

(已過期)。(

HTTP/2.0

是在

HTTP/1.1

的基礎上更新處理的,不在該繼承體系内,

APR

采用

Apache

可移植運作庫(

c++

編寫的本地庫,就是

native

方法)實作的,官方已标注過期不建議使用,是以不過多解釋)

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

Tomcat采用

UpgradeProtocol

表示HTTP更新協定,目前隻有一個實作類

Http2Protocol

用于處理

HTTP/2.0

。它根據請求建立一個用于更新處理的令牌

UpgradeToken

,該令牌包含了具體的HTTP更新處理器

HttpUpgradeHandler

HTTP/2.0

的處理器為

Http2UpgradeHandler

)。(Tomcat中

WebSocket

也是通過

UpgradeToken

機制實作的,其處理器為

WsHttpUpgradeHandler

1、Endpoint通信端點

Endpoint

負責網絡通信,監聽一個端口,循環接收

socket

請求,讀取網絡位元組流等。

Endpoint

不是接口,而是提供了一個抽象類

AbstractEndpoint

,又根據I/O方式提供了若幹實作類:

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?
Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

網絡通信這一層是非常抽象的,傳輸層協定毋庸置疑是

TCP/IP

,但是如何監聽socket請求,讀取網絡位元組流的方式是多樣的,同步非阻塞、I/O多路複用、異步非阻塞等等:

  • Endpoint

    高度抽象出一個

    Acceptor

    來循環監聽

    socket

    請求;
  • Tomcat是支援高并發的,但是機器的性能是有限的,為了保證Web伺服器不被高流量沖垮,是以在接收請求前會有一個

    LimitLatch

    限流器(利用

    AQS

    實作);
  • 接收到的

    socket

    通道(

    SocketChannel

    or

    AsynchronousSocketChannel

    ),會先根據I/O方式包裝一下,如同步非阻塞

    NioChannel

    、異步非阻塞

    Nio2Channel

    等;
  • 包裝之後的

    socket

    通道又用一個更抽象的

    SocketWrapper

    封裝,以應對不同方式的網絡位元組流的讀取和寫入;
  • 出于高并發的設計,将抽象的

    SocketWrapper

    交由

    SocketProcessor

    任務對象處理,

    SocketProcessor

    會扔進一個線程池

    Executor

    處理;
  • 為了進一步提高性能,每次處理請求都會把一些對象緩存起來,不重複建立,比如

    SocketWrapper

    SocketProcessor

    等。
Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

2、Processor應用層協定解析

Acceptor

接收到請求封裝成一個

SocketProcessor

扔進線程池

Executor

後,會調用

Processor

從作業系統底層讀取、過濾位元組流,對應用層協定(

HTTP/AJP

)進行解析封裝,生成

org.apache.coyote.Request

org.apache.coyote.Response

對象。不同的協定有不同的

Processor

HTTP/1.1

對應

Http11Processor

AJP

對應

AjpProcessor

HTTP/1.2

對應

StreamProcessor

UpgradeProcessorInternal

UpgradeProcessorExternal

用于協定更新:

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

(1)ConnectionHandler建立合适的Processor

SocketProcessor

并不是直接調用的

Processor

,而是通過

org.apache.coyote.AbstractProtocol.ConnectionHandler#process

找到一個合适的

Processor

進行請求處理:

  • 根據不同協定建立

    Http11Processor

    or

    AjpProcessor

  • 根據協定更新是内部更新(

    HTTP/2.0

    )還是外部更新建立

    UpgradeProcessorInternal

    or

    UpgradeProcessorExternal

(2)協定更新

如果是正常的協定,如

HTTP/1.1

AJP/1.3

,則

Processor#process

處理完請求後會直接調用

Adapter#service

,将請求轉發給

Container

如果是協定更新(除

Websocket

),首先通過HTTP/1.1進行協定更新:

  • 伺服器接收到帶有特殊請求頭(

    Upgrade

    )的

    HTPP/1.1

    連接配接,是以仍會先交給

    Http11Processor

    進行處理;
  • 根據請求頭

    Upgrade

    對應協定名建立

    UpgradeToken

    ,并指派給目前

    Processor

  • 傳回

    SocketState.UPGRADING

    ,再由

    ConnectionHandler

    進行協定更新;
  • ConnectionHandler

    會從目前

    Processor

    擷取

    UpgradeToken

    對象(如果沒有,則預設為HTTP/2.0),并建構一個更新的

    Processer

    (若為Tomcat可以處理的協定更新(

    HTTP/2.0

    WebSocket

    ) ,則是

    UpgradeProcessorInternal

    ,否則為

    UpgradeProcessorExternal

    )。
  • 替換目前

    Processer

    ,并将目前

    Processer

    釋放回收;
  • UpgradeProcessor

    設定給

    UpgradeToken

    中的

    HttpUpgradeHandler

    ,并調用

    HttpUpgradeHandler.init

    進行初始化,開啟更新協定的處理。
  • 由于

    HTTP/2.0

    是多路複用協定,一個連接配接可以處理多個HTTP請求,是以對于

    Http2UpgradeHandler

    ,會将每次請求響應交于

    StreamProcessor

    處理,再由

    StreamProcessor

    将請求送出給

    Container

注意:

  • UpgradeProcessorInternal

    UpgradeProcessorExternal

    都實作了接口

    WebConnection

    ,表示一個用于更新的連接配接,并不處理協定更新後資料讀寫和解析,而是交由

    HttpUpgradeHandler

    ,對于

    Http2UpgradeHandler

    再建構

    StreamProcessor

    ,又将

    StreamProcessor

    包裝成任務類

    StreamRunnable

    ,扔進線程池

    Executor

    處理。
  • WebSocket

    也屬于協定更新,和

    HTTP/2.0

    更新方案一緻,但是協定更新的判斷機制有所不同,

    WebSocket

    更新判斷不在連接配接器裡,而是交由

    Servlet

    容器通過目前請求的過濾器

    WsFilter

    判斷,如果是

    WebSocket

    協定更新,則調用目前

    org.apache.catalina.connector.Request#upgrade

    建構

    UpgradeToken

    并傳遞給

    Http11Processor

    處理(調用鈎子函數

    org.apache.coyote.AbstractProcessor#action

    ),之後到了

    ConnectionHandler

    邏輯就跟

    HTTP/2.0

    更新差不多了。

3、配置方式

<Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000" 
           executor="tomcatThreadPool" 
           redirectPort="8443">
     <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/>
</Connector>
           
  • port

    Connector

    監聽的端口。
  • protocol

    是應用層協定名,可填參數有

    HTTP/1.1

    org.apache.coyote.http11.Http11NioProtocol

    AJP/1.3

    org.apache.coyote.ajp.AjpNioProtocol

    ,如果

    protocol

    不填,則預設為

    Http11NioProtocol

  • connectionTimeout

    表示

    Connector

    接收到連接配接後等待逾時時間,機關毫秒,預設20秒。
  • executor

    表示使用一個共享線程池,若使用私有線程池,則

    executor

    不需要指定,私有線程池可選參數有

    minSpareThreads=“10”

    maxThreads=“200”

  • redirectPort

    表示非

    SSL

    重定向到

    SSL

    端口,當請求是

    non-SSL

    請求,但是接收到的請求内容需要

    SSL

    傳輸,則重定向到

    SSL

    端口。
  • 若為

    HTTP

    開啟

    HTTP/2.0

    支援,需要配置

    UpgradeProtocol

    Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

四、Adapter

Adapter

接口隻有一個實作類

org.apache.catalina.connector.CoyoteAdapter

,其主要職責如下:

  • org.apache.coyote.Request

    org.apache.coyote.Response

    轉為實作了标準

    Servlet

    org.apache.catalina.connector.Request

    org.apache.catalina.connector.Response

  • 将請求體的

    serverName

    URI

    version

    傳給

    Mapper

    元件做映射,比對到合适的

    Host

    Context

    Wrapper

  • Request

    Response

    傳給

    Container

    處理,

    Engine

    通過管道

    Pipeline

    傳給

    Host

    Host

    再傳給

    Context

    Context

    再傳給

    Wrapper

    Wrapper

    是最終的

    Servlet

五、請求處理完整流程

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

六、要點回顧

任重而道遠啊,本篇主要從

Connector

的整體架構進行講解,主要弄明白

Connector

是如何設計的?為什麼要這樣設計?

Connector

作為獨立子產品,封裝底層網絡通信,使

Container

和具體的協定及I/O方式解耦,易擴充、高性能。這也使得架構變得抽象複雜,

Connector

需要應對多種協定和I/O方式的組合,高度的抽象和封裝。

Connector

隻負責接收和解析請求,具體的業務處理還需要交給

Container

,是以需要一個擴充卡作為

Connector

Container

連接配接的橋梁。

很多實作細節并沒有展開,比如:

  1. Endpoint

    的工作原理沒有展開,如何監聽請求?如何限流?如何封裝接收到的

    socket

    ?如何将連接配接扔進線程池裡處理?
  2. Processor

    應用層協定解析原理沒有展開,如何讀取底層位元組流?如何解析封裝應用層協定?如何封裝生成

    Request

    Response

  3. 擴充卡

    Adapter

    如何做轉發?如何找到對應的

    Host

    Context

    Wrapper

涉及的知識點很多,比如:

  1. 對傳輸層協定

    TCP/IP

    的了解。
  2. 對I/O模型的了解(I/O多路複用、同步非阻塞、異步非阻塞等)。
  3. 對傳輸層協定的了解(HTTP協定真的要掌握,不然真看不懂代碼是怎麼解析請求行、請求頭、請求體的)。
  4. 高并發架構設計(緩存、

    Reactor

    響應式、傳輸層協定延遲加載)。

後續會一一講解。

Tomcat連接配接器Connector源碼解讀(一)架構概覽,如何設計?為什麼這樣設計?

七、參考文獻

  1. 書籍:《Tomcat架構解析》劉光瑞(Tomcat8.5)
  2. 書籍:《Tomcat核心設計剖析》汪建(Tomcat7)
  3. 極客時間:《深入拆解Tomcat & Jetty》李号雙(Tomcat9.x)
  4. Tomcat源碼:https://gitee.com/stefanpy/tomcat-source-code-learning (Tomcat10.0.6)

如若文章有錯誤了解,歡迎批評指正,同時非常期待你的留言和點贊。如果覺得有用,不妨點個在看,讓更多人受益。

繼續閱讀