天天看點

Tomcat處理HTTP請求源碼分析(下)

很多開源應用伺服器都是內建tomcat作為web container的,而且對于tomcat的servlet container這部分代碼很少改動。這樣,這些應用伺服器的性能基本上就取決于Tomcat處理HTTP請求的connector子產品的性能。本文首先從應用層次分析了tomcat所有的connector種類及用法,接着從架構上分析了connector子產品在整個tomcat中所處的位置,最後對connector做了詳細的源代碼分析。并且我們以Http11NioProtocol為例詳細說明了tomcat是如何通過實作ProtocolHandler接口而建構connector的。

上篇位址為《​​Tomcat處理HTTP請求源碼分析(上)​​》 ,本文是系列下篇。

由上面的介紹我們可以知道,實作Connector就是實作ProtocolHander接口的過程。

AjpAprProtocol、AjpProtocol、Http11AprProtocol、Http11Protocol、JkCoyoteHandler、MemoryProtocolHandler這些實作類的實作流程與Http11NioProtocol相同,下面我們以Http11NioProtocol為類重點說明tomcat中如何實作ProtocolHander接口的。

Http11NioProtocol實作了ProtocolHander接口,它将所有的操作委托給NioEndpoint類去做,如下圖:

NioEndpoint類中的init方法中首先以普通阻塞方式啟動了SocketServer:

NioEndpoint類的start方法是關鍵,

可以看出,在start方法中啟動了兩個線程和一個線程池:

Acceptor線程,該線程以普通阻塞方式接收用戶端請求(socket.accep()),将客戶Socket交由線程池是處理,線程池要将該Socket配置成非阻塞模式(socket.configureBlocking(false)),并且向Selector注冊READ事件。該線程數目可配置,預設為1個。

Poller線程,由于Acceptor委托線程為用戶端Socket注冊了READ事件,當READ準備好時,就會進入Poller線程的循環,Poller線程也是委托線程池去做,線程池将NioChannel加入到ConcurrentLinkedQueue<NioChannel>隊列中。該線程數目可配置,預設為1個。

線程池,就是上面說的做Acceptor與Poller線程委托要做的事情。

在Init接口實作方法中阻塞方式啟動ServerSocketChannel。

Start方法中啟動了線程池,acceptor線程與Poller線程。其中acceptor與poller線程一般數目為1,當然,數目也可配置。

可以看出,線程池有兩種實作方式:

普通queue + wait + notify方式,預設使用的方式,據說實際測試這種比下種效率高

JDK1.5自帶的線程池方式

在Acceptor線程中接收了客戶請求,同時委托線程池注冊READ事件。

在Acceptior線程中接收了客戶請求(serverSock.accept())

委托線程池處理

線上程池的Worker線程的run方法中有這麼幾句:

在setSocketOptions方法中,首先将socket配置成非阻塞模式:

在setSocketOptions方法中,最後調用getPoller0().register(channel);一句為SocketChannel注冊READ事件,register方法代碼如下(注意:這是Poller線程的方法):

其中attachment的結構如下,它可以看做是一個共享的資料結構:

在上面說的setSocketOptions方法中調用Poller線程的register方法注冊讀事件之後,當READ準備就緒之後,就開始讀了。下面代碼位于Poller線程的run方法之中:

可以看到,可讀之後調用processSocket方法,該方法将讀處理操作委拖給線程池處理(注意此時加入到線程池的是NioChannel,不是SocketChannel):

線程池的Worker線程中的run方法中的部分代碼如下(請注意handler.process(socket)這一句):

注意:

調用了hanler.process(socket)來生成響應資料)

資料生成完之後,注冊WRITE事件的,代碼如下:

NioEndpoint類中的Handler接口定義如下:

其中process方法通過Adapter來調用Servlet Container生成傳回結果。Adapter接口定義如下:

實作一個tomcat連接配接器Connector就是實作ProtocolHander接口的過程。Connector用來接收Socket Client端的請求,通過内置的線程池去調用Servlet Container生成響應結果,并将響應結果同步或異步的傳回給Socket Client。在第三方應用內建tomcat作為Web容器時,一般不會動Servlet Container端的代碼,那麼connector的性能将是整個Web容器性能的關鍵。