1. 程式人生 > >Jetty之接受請求。

Jetty之接受請求。

Jetty作為一個獨立的Servlet引擎,可以獨立提供Web服務,但是他也可以與其他Web應用伺服器繼承,所以呀可以基於兩種協議工作,一種是HTTP,另一種是AJP。如果將Jetty整合到JBoss或者Apache,那麼就可以讓Jetty基於AJP模式工作。下面分別介紹Jetty是如何基於這兩種協議工作得,以及他們是如何建立連線和接受請求的。

基於HTTP工作

如果在前端沒有其他Web伺服器,那麼Jetty應該基於HTTP工作。也就是當Jetty接收到一個請求時,必須按照HTTP解析請求和封裝返回的資料。那麼Jetty是如何接收一個連線又是如何處理這個連線的呢?

我們設定Jetty的Connector實現類為org.eclipse.jetty.server.bi.SocketConnector,讓Jetty以BIO的方式工作。Jetty在啟動時將會建立BIO的工作環境,他會建立HttpConnection類來解析和封裝HTTP1.1的協議,ConnectorEndPoint類是以BIO的處理方式處理連線請求的,ServerSocket用於建立Socket連線以接收和傳送資料,Executor用於處理連線的執行緒池,他負責處理每一個請求佇列的任務。acceptorThread監聽連線請求,一有Socket連線,他便將進入下面的處理流程。

當Socket被真正執行時,HttpConnection將被呼叫,這裡定義瞭如何將請求傳遞到Servlet容器裡,以如何將請求最終路由到目的Servlet。

下圖是Jetty啟動建立連線的時序圖。

Jetty建立連線的環境需要以下三個步驟。

  1.     建立一個佇列執行緒池,用於處理每個建立連線的任務,這個執行緒池可以由使用者來指定,這和Tomcat是類似的。
  2.     建立ServerSocket,用於準備接收客戶端的Socket請求,以及客戶端用來包裝這個Socket的一些輔助類。
  3.     建立一個或多個監聽執行緒,用來監聽訪問埠是否有連線進來。

相比Tomcat建立連線的環境,Jetty的邏輯更加簡單,牽涉的類更少,執行的程式碼量頁更少。

當建立連線的環境已經準備好時,就可以接收HTTP請求了,當Acceptor接收到Socket連線後將轉入下圖所示的流程執行。

Acceptor執行緒將會為這個請求建立ConnectorEndPoint,HttpConnection用來表示這個連線是一個HTTP的連線,他會建立HttpParse類解析HTTP,並且會建立符合HTTP的Request和Response物件。接下來就是將這個執行緒交給佇列執行緒池去執行了。

基於AJP工作

通常一個Wb服務站點的後端伺服器不是將Java的應用伺服器直接暴露給服務訪問者,而是在應用伺服器(如Jboss)的前面再加一個Web伺服器(如Apache或者Nginx),對原因大家應該很容易理解,如做日誌分析、負載均衡、許可權控制、防止惡意請求以及靜態資源預載入等。

下圖是通常的Web服務端的架構圖。

在這種架構下Servlet引擎就不需要解析和封裝返回的HTTP,因為HTTP的解析工作已經在Apache或Nginx伺服器上完成了,JBoss只要基於更加簡單的Ajp工作就行了,這樣能加快請求的響應速度。

對比HTTP的時序圖可以發現,他們的邏輯幾乎是相同的,不同的是替換了一個類,即Ajp13Parserer替換了HttpParser,他定義瞭如何處理AJP及需要哪些類來配合。

實際上Ajp處理請求相比於HTTP唯一的不同就是在讀取到Socket資料包時如何來轉換這個資料包,按照HTTP的包格式來解析就是HttpParser,按照AJP來解析就是Ajp13Parserer。封裝返回的資料也是如此。

讓Jetty工作在AJP下,需要配置connector的實現類為Ajp13SocketConnector,這個l欸繼承了SocketConnector物件而不是HttpConnection。下圖所示的是Jetty建立連線環境的時序圖。

與Http方式唯一不同的地方就是將SocketConnector類替換成了Ajp13SocketConnector類,改成Ajp13SocketConnector的目的就是可以建立Ajp13Connection類,表示當前這個連線使用的是AJP,所以需要用Ajp13Parser類來解析AJP,處理連線的邏輯都是一樣的。AJPParser類解析AJP的時序圖如下圖所示。

基於NIO方式工作

前面所描述的從Jetty建立客戶端連線到處理客戶端連線都是基於BIO的方式,他也支援另外一種NIO的處理方式,其中Jetty的預設connector就是NIO方式。

通常NIO的工作原型如下:

建立一個Selector相當於一個觀察者開啟一個Server端通道,把這個Server通道註冊到觀察者上並且指定監聽事件,然後遍歷這個觀察者觀察到的事件,取出感興趣的事件再處理。這裡有個最核心的地方就是,我們不需要為每個被觀察者為每個被觀察者建立一個執行緒來監控他隨時發生的事情,而是把這些被觀察者都註冊一個地方統一管理,再由他把觸發的事件統一發送給感興趣的程式模組。這裡的核心是能夠統一的管理每個被觀察者的事件,所以我們就可以把服務端的每個建立的連線傳送和接受資料作為一個事件統一管理,這樣就不必每個連線需要一個執行緒來維護了。

這裡需要注意的是,很多人認為監聽SelectionKey.OP_ACCEPT事件就已經是非阻塞方式了,其實Jetty仍然用一個執行緒來監聽客戶端的連線請求,當接收到請求後,把這個請求再註冊到Selector上,然後才以非阻塞方式執行。這裡還有一個容易引起誤解的地方,即認為Jetty以NIO方式工作,只會有一個執行緒來處理所有的請求,甚至認為不同的使用者會在服務端共享一個執行緒從而導致基於ThreadLocal的程式出現問題。其實從Jetty的原始碼中能夠發現,真正共享一個執行緒的處理只是在監聽不同連線的資料傳送事件上,如有多個連線已經建立,傳送方式是當沒有資料傳輸時,執行緒是阻塞的,也就是一直在等待下一個資料的到來,而NIO的處理方式是隻有一個執行緒在等待所有連線的資料的到來,而當某個連線資料到來時,Jetty會把他分配給這個連線對應的處理執行緒去處理,所以不同連線的處理執行緒仍然是獨立的。

Jetty的NIO處理方式和Tomcat的幾乎一樣,唯一不同的地方是如何把監聽到的事件分配給對應的連線處理。從測試效果來看,Jetty的NIO處理方式更加高效。下圖是Jetty的NIO處理方式的時序圖。