很多開源應用伺服器都是內建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容器性能的關鍵。