Tomcat源碼解析系列(十一)ProtocolHandler


前言
上篇文章中講到了 Connector 的初始化與啟動,其中最關鍵的就是 ProtocolHandler 的初始化與啟動。tomcat 中 ProtocolHandler 的默認實現類是 Http11NioProtocol。tomcat9.0.16 中 ProtocolHandler 的實現類中還有一個 Http11Nio2Protocol,兩者實現上類似。這兩個實現的的父類都是 AbstractHttp11JsseProtocol,AbstractHttp11JsseProtocol 的父類是 AbstractHttp11Protocol,AbstractHttp11Protocol 的父類是 AbstractProtocol。ProtocolHandler來處理網絡連接和應用層協議,包含兩個重要組件:endpoint和processor,endpoint是通信端點,即通信監聽的接口,是具體的socket接受和發送處理器,是對傳輸層的抽象,processor接受來自endpoint的socket,讀取字節流解析成Tomcat的request和response對象,並通過adapter將其提交到容器處理,processor是對應用層協議的抽象。


1. Http11NioProtocol 構造方法
在 Connector 的構造方法中,用反射創建了一個 Http11NioProtocol 對象。

public Http11NioProtocol() { super(new NioEndpoint()); }
public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S,?> endpoint) { super(endpoint); }
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) { super(endpoint); setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); ConnectionHandler<S> cHandler = new ConnectionHandler<>(this); setHandler(cHandler); getEndpoint().setHandler(cHandler); }
/** * Endpoint that provides low-level network I/O - must be matched to the * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO * Endpoint etc.). */ private final AbstractEndpoint<S,?> endpoint; public AbstractProtocol(AbstractEndpoint<S,?> endpoint) { this.endpoint = endpoint; setConnectionLinger(Constants.DEFAULT_CONNECTION_LINGER); setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); } public void setConnectionLinger(int connectionLinger) { endpoint.setConnectionLinger(connectionLinger); } public void setTcpNoDelay(boolean tcpNoDelay) { endpoint.setTcpNoDelay(tcpNoDelay); }

在 Http11NioProtocol 構造方法里,創建了一個 NioEndpoint 對象,Http11Nio2Protocol 與 Http11NioProtocol 的區別主要在這里,Http11Nio2Protocol 在構造方法里創建的是 Nio2Endpoint 對象。這個 NioEndpoint 對象是非常重要的組件,它封裝了 tomcat 的線程模型,后面會單獨講解這個類,這里先不多做描述。
在 AbstractProtocol 里把這個 NioEndpoint 對象復制給內部的 Endpoint 類型的屬性,然后設置了一些 Endpoint 對象的兩個屬性,setConnectionLinger 和 setTcpNoDelay 方法就是調用 Endpoint 對象的相關方法。

在 Http11NioProtocol 父類的父類 AbstractHttp11Protocol 里,創建了一個 ConnectionHandler 對象,並調用 setHandler(cHandler) 把這個對象賦值給自己 handler 屬性,這個屬性在 AbstractHttp11Protocol 的父類 AbstractProtocol 里。

private Handler<S> handler; protected void setHandler(Handler<S> handler) { this.handler = handler; }

ConnectionHandler 是 AbstractProtocol 類的一個靜態內部類,其聲明為

rotected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S>

可以看出 Handler 類是 AbstractEndpoint 里的一個靜態的接口類型,這個 Handler 接口定義了一些處理 Socket 事件的方法。
AbstractHttp11Protocol 構造方法里接着調用了 getEndpoint().setHandler(cHandler),把 ConnectionHandler 對象賦值 NioEndpoint 的 Handler 類型的屬性。


2. ProtocolHandler#init 方法
ProtocolHandler 定義了 init 和 start 方法,ProtocolHandler 的實現類 AbstractProtocol 及其子類 AbstractHttp11Protocol 實現或重載了 init 方法,AbstractHttp11Protocol 的子類都沒有重載 init 方法。

2.1. AbstractHttp11Protocol#init


/** * The upgrade protocol instances configured. */ private final List<UpgradeProtocol> upgradeProtocols = new ArrayList<>(); @Override public void init() throws Exception { // Upgrade protocols have to be configured first since the endpoint // init (triggered via super.init() below) uses this list to configure // the list of ALPN protocols to advertise for (UpgradeProtocol upgradeProtocol : upgradeProtocols) { configureUpgradeProtocol(upgradeProtocol); } super.init(); }

AbstractHttp11Protocol#init 方法比較簡單,就是簡單調用了 configureUpgradeProtocol 方法。

/**  * The protocols that are available via internal Tomcat support for access  * via HTTP upgrade. */ private final Map<String,UpgradeProtocol> httpUpgradeProtocols = new HashMap<>(); /**  * The protocols that are available via internal Tomcat support for access  * via ALPN negotiation. */ private final Map<String,UpgradeProtocol> negotiatedProtocols = new HashMap<>(); private void configureUpgradeProtocol(UpgradeProtocol upgradeProtocol) { // HTTP Upgrade String httpUpgradeName = upgradeProtocol.getHttpUpgradeName(getEndpoint().isSSLEnabled()); boolean httpUpgradeConfigured = false; if (httpUpgradeName != null && httpUpgradeName.length() > 0) { httpUpgradeProtocols.put(httpUpgradeName, upgradeProtocol); httpUpgradeConfigured = true; getLog().info(sm.getString("abstractHttp11Protocol.httpUpgradeConfigured", getName(), httpUpgradeName)); } // ALPN String alpnName = upgradeProtocol.getAlpnName(); if (alpnName != null && alpnName.length() > 0) { if (getEndpoint().isAlpnSupported()) { negotiatedProtocols.put(alpnName, upgradeProtocol); getEndpoint().addNegotiatedProtocol(alpnName); getLog().info(sm.getString("abstractHttp11Protocol.alpnConfigured", getName(), alpnName)); } else { if (!httpUpgradeConfigured) { // ALPN is not supported by this connector and the upgrade // protocol implementation does not support standard HTTP // upgrade so there is no way available to enable support // for this protocol. getLog().error(sm.getString("abstractHttp11Protocol.alpnWithNoAlpn", upgradeProtocol.getClass().getName(), alpnName, getName())); } } } }

configureUpgradeProtocol 方法也挺簡單的,就是將 UpgradeProtocol 放在 httpUpgradeProtocols 和 negotiatedProtocols 里。

2.2. AbstractProtocol#init

@Override public void init() throws Exception { if (getLog().isInfoEnabled()) { getLog().info(sm.getString("abstractProtocolHandler.init", getName())); logPortOffset(); } if (oname == null) { // Component not pre-registered so register it oname = createObjectName(); if (oname != null) { Registry.getRegistry(null, null).registerComponent(this, oname, null); } } if (this.domain != null) { rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName()); Registry.getRegistry(null, null).registerComponent( getHandler().getGlobal(), rgOname, null); } String endpointName = getName(); endpoint.setName(endpointName.substring(1, endpointName.length()-1)); endpoint.setDomain(domain); endpoint.init(); }

AbstractProtocol#init 里前面幾行是注冊一些對象到 MBeanServer 里,最重要的是最后一行的 endpoint.init(),這一行調用了 NioEndpoint 的 init 方法。關於 NioEndpoint 的詳細內容將在后續的文章里講解。


3. ProtocolHandler#start 方法
ProtocolHandler 的實現類 AbstractProtocol 實現了 start 方法,AbstractProtocol 的子類並沒有重載 start 方法。

@Override public void start() throws Exception { if (getLog().isInfoEnabled()) { getLog().info(sm.getString("abstractProtocolHandler.start", getName())); logPortOffset(); } endpoint.start(); monitorFuture = getUtilityExecutor().scheduleWithFixedDelay( new Runnable() { @Override public void run() { if (!isPaused()) { startAsyncTimeout(); } } }, 0, 60, TimeUnit.SECONDS); }

在 AbstractProtocol#start 方法里先調用了 endpoint.start(),然后使用線程池來定期調用 startAsyncTimeout() 方法。
這里的 getUtilityExecutor() 返回的對象,是在 Connector 的 initInternal 中調用 ProtocolHandler#setUtilityExecutor 設值的,傳入的對象 StandardServer 的 utilityExecutorWrapper,這個在之前介紹 Server 的文章里講到過。
endpoint.start() 會在后續的文章里講解。

protected void startAsyncTimeout() { if (asyncTimeoutFuture == null || (asyncTimeoutFuture != null && asyncTimeoutFuture.isDone())) { if (asyncTimeoutFuture != null && asyncTimeoutFuture.isDone()) { // There was an error executing the scheduled task, get it and log it try { asyncTimeoutFuture.get(); } catch (InterruptedException | ExecutionException e) { getLog().error(sm.getString("abstractProtocolHandler.asyncTimeoutError"), e); } } asyncTimeoutFuture = getUtilityExecutor().scheduleAtFixedRate( new Runnable() { @Override public void run() { long now = System.currentTimeMillis(); for (Processor processor : waitingProcessors) { processor.timeoutAsync(now); } } }, 1, 1, TimeUnit.SECONDS); } }

可以看出 startAsyncTimeout 方法的作用是定期調用 waitingProcessors 里的 Processor 對象的 timeoutAsync 方法來處理一些超時的請求。
Processor 也是 tomcat 用來處理請求的一個關鍵組件。上文中提到的 ConnectionHandler 就是使用 Processor 來具體處理請求的。Processor 將會在后續的文章中介紹。

 

請求到達Poller處理,最終是由Processor來進行處理,為了說明這中間過程所涉及的部分,先整理下在tomcat服務的各個組成部分:ProtocolHandler、Endpoint、Endpoint.Handler、Processor

它們之間的引用關系如下

[ProtocolHandler]     <---------   Connector

org.apache.coyote.ProtocolHandler
org.apache.coyote.AbstractProtocol
org.apache.coyote.http11.AbstractHttp11Protocol
org.apache.coyote.http11.AbstractHttp11JsseProtocol
org.apache.coyote.http11.Http11NioProtocol    *

 

[Endpoint]           <---------   ProtocolHandler(Http11NioProtocol)
 
org.apache.tomcat.util.net.AbstractEndpoint 
org.apache.tomcat.util.net.NioEndpoint      *

處理連接數的控制、連接的建立等工作,主要包括Acceptor和Poller兩大部分,將建立好的連接交由Endpoint的Handler去處理


[Endpoint.Handler]     <---------   Endpoint(NioEndpoint)

     AbstractEndpoint.Handler
     AbstractConnectionHandler    < -- > AbstractProtocol
     Http11ConnectionHandler      < -- >  Http11NioProtocol    *

 緩存連接和Processor的關系,根據連接尋找Processor來處理


[Processor]      <---------   Endpoint.Handler

org.apache.coyote.Processor
org.apache.coyote.AbstractProcessor
org.apache.coyote.http11.AbstractHttp11Processor
org.apache.coyote.http11.Http11NioProcessor    *

(process方法)處理http請求中的相關業務,服務狀態,協議解析,請求握手,內容解壓等,構造適合Adapter處理request和response對象,然后調用Adater進行業務數據處理。


Acceptor和Poller都是屬於Endpoint的內部組成部分,所以,這里是socket連接有數據到達時,被交到Endpoint.Handler中,經過緩存加速,找到對應的Processor之后,交由Processor來處理。

Processor處理完http協議相關的內容后,交由Adapter來處理業務。

Q&A

1、在什么時候讀取請求數據?

     Java Servlet開發模型中,由開發者自定義的servlet來通過InputStream讀取數據,通過OutputStream寫入數據。所以,以上的這些部分,都不涉及到具體業務數據的讀寫。具體的數據讀寫時機,在Adapter之后。

2、請求如何被轉到用戶自定義的Servlet上的?

     具體的實現,在Adapter之后的部分,這里並不涉及與此相關的部分。

3、服務的連接數是如何控制的?

     Endpoint內部的Acceptor來控制,Endpoint提供了setMaxConnection方法來設置tomcat所能支持的最大連接數。此方法會設置一個Acceptor用的一個LimitLatch鎖。通過LimitLatch來控制連接的數量。當有新連接加入時,會增加計算器;連接關閉時,會將計數器減一。

4、tomcat服務內在哪個地方使用了線程池?
   
     Endpoint內部的Poller用來選出有數據到達的socket連接,並將連接交給Endpoint.Handler來進行處理。這個傳遞事件被封裝成了一個SocketProcessor任務,通過執行SocketProcessor任務來完成。
     SocketProcessor的執行時再線程池中實現的,也就是Endpoint的Poller線程,將事件轉成SocketProcessor任務后,放在Endpoint的ThreadPool中完成。

小結
本文介紹了 ProtocolHandler 的初始化和啟動,ProtocolHandler的默認實現類是 Http11NioProtocol。Http11NioProtocol 有一個非常重要的 NioEndpoint 對象,ProtocolHandler 的 init 和 start 方法中最關鍵的就是調用這個 NioEndpoint 對象的 init 和 start 方法。此外,在 AbstractHttp11Protocol 構造方法里創建了一個也是非常重要的 ConnectionHandler 對象,這個對象是用來處理請求,ConnectionHandler 使用 Processor 對象來具體處理請求。
原文鏈接:https://blog.csdn.net/yanlinwang/article/details/46537735

https://blog.csdn.net/yanlinwang/article/details/46537735?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-9.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-9.nonecase


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM