1. 程式人生 > >Tomcat基於Coyote的聯結器原始碼分析

Tomcat基於Coyote的聯結器原始碼分析

不論Tomcat的容器設計得如何精妙,本質上Tomcat就是個HTTP伺服器,需要從socket中獲得HTTP資料流;另一方面,容器只能處理封裝好的org.apache.coyote.Request,從socket到Request之間需要有個轉換過程。因此,連線socket和容器之間的重任就交給了Coyote

Coyote簡介

Coyote是Tomcat的Connector框架的名字,簡單說就是Coyote來處理底層的socket,並將HTTP請求、響應等位元組流層面的東西,包裝成Request和Response兩個類(這兩個類是tomcat定義的,而非servlet中的ServletRequest和ServletResponse),供容器使用;同時,為了能讓我們編寫的servlet能夠得到ServletRequest,Tomcat使用了Facade模式,將比較底層、低階的Request包裝成為ServletRequest(這一過程通常發生在Wrapper容器一級),這也是為很多人津津樂道的tomcat對設計模式的一個巧妙的運用,具體過程將會在以後討論。

所以,coyote本質上是為tomcat的容器提供了對底層socket連線資料的封裝,以Request類的形式,讓容器能夠訪問到底層的資料。

而關於連線池、執行緒池等直接和socket打交道的事情,tomcat交給了org.apache.tomcat.util.net包的類去完成,這裡介紹其中幾個核心類

org.apache.coyote

這個包裡面的主要是coyote框架的介面

Adapter

“介面卡”在這裡的意思,是指“凡是使用Coyote聯結器的容器,都要實現這個介面,以便從Coyote聯結器接收請求和響應資料”,當然這裡的請求和響應是org.apache.coyote.Request和Response

ProtocolHandler

每個ProtocolHandler,代表著對一種協議的支援,比如tomcat預設支援的協議有http1.1和ajp。根據支援的協議,ProtocolHandler裡面通常包含了一個實現對應協議的Handler介面的處理類,用於接收socket物件,再交給對應協議的Processor類(然而這個Processor類沒有實現Processor介面,而是實現了ActionHook介面),最後由Processor類交給實現了Adapter介面的容器(準確的說是該容器的Pipeline的第一個Valve)

Processor

這個介面已經廢棄了,通常Tomcat的Processor實現類實現的介面是ActionHook。你會看到許多名為“XXXProcessor”的類,但其實他們實現的介面卻是ActionHook

ActionHook

本介面代替了Processor介面,成為所有Processor實現類的標準介面。其方法只有一個:public void action( ActionCode actionCode, Object param);
ActionCode是一個靜態類,說白了是一堆常量(估計以後會改成enum),即對應不同的ActionCode,ActionHook要作出不同的動作,至於param是用於傳遞一些資訊的,通常會把呼叫者“this”傳遞進去

InputBuffer和OutputBuffer

兩個介面都只有一個方法,分別是doread和dowrite,就是把資料從ByteChunk引數讀出或者寫入ByteChunk。然而“資料”從何而來、怎樣寫進ByteChunk,還得看不同的類實現

Request和Response

Request這個類可謂tomcat的一大核心,幾乎所有Connector和容器都要用到它

Request類實現了對底層http位元組流的封裝,因為http本質上是從網路過來的一串位元組流,並且從邏輯上根據http協議,分成了頭和體,其中頭部又有很多欄位(包括MIME欄位)。而Request的作用就是把這些位元組封裝成對應的欄位,並且達到處理效率的最優

因此,Request裡面大部分方法是欄位的get方法(set方法不多,因為大部分欄位是不可改變的),此外還有提供給容器使用的方法,如recycle、inputbuffer等等。但最關鍵的是,Request是如何提高處理效率的

對於底層的、和位元組流打交道的DO(data object),效能瓶頸在於對記憶體的使用上(因為位元組都是放在一塊塊的記憶體中),如果能有效的使用記憶體,就能有效地提高DO的效能。

如果讓我們來實現這個Request類,估計大部分人第一反應就是用String來表示每個http頭欄位,然而String的效率之低下是絕對無法勝任伺服器的效能要求的。Request的註釋告訴我們,它的大部分欄位是“GC free”的,即很少、甚至不會被垃圾回收。杜絕了java中最大的一個性能瓶頸,Request自然效能得到大幅提升。

此外,其欄位的一些耗時操作都會延遲到使用者程式碼一級,也就是說,Tomcat內部在使用Request時,都會盡量保證它的欄位處於原始的位元組狀態(而不是圖方便到處使用String),直到使用者程式碼(也就是我們寫的servlet)需要時才進行轉換,如果用不到(其實http請求的大部分欄位在我們程式設計時都用不到),就不作轉換。這樣又進一步挖掘出更多的效能潛力,其思想和“延遲載入”的設計模式如出一轍。

當然,Tomcat的程式設計師也是人也喜歡偷懶,誰都不樂意直接操縱位元組陣列,那樣出錯的風險也大。因此,tomcat的org.apache.tomcat.util包定義了許多底層的工具類,用於操作和維護位元組陣列。Request的欄位們的型別為MessageBytes就是其中的一種

而關於Response,原理和Request類似,但是Response簡單了很多,最明顯的是裡面的欄位不像Request那樣為效率絞盡腦汁,而是直接用了String,也許是Response對整體效率影響不大,亦或者當前版本的tomcat還未對其進行改造。

Coyote框架總結

在Coyote框架中,最吸引我的有三個地方:recycle機制,ByteChunk和MessageBytes。

  1)由於Java自動記憶體回收機制效率不高,有很多問題,所以Coyote中通過recycle機制的使用,及時進行引數初始化和記憶體釋放。以Request為例,Recycle函式中主要進行了三個方面的事情:自身引數的初始化,自身建立的資源的釋放和呼叫類中使用的引用物件的recycle函式。通過遞迴地進行recycle,一方面及時並且全面地釋放了不再需要的資源,另一方面及時對相關引數進行初始化,提高下一次類訪問的執行速度。

  2)對於底層的、和位元組流打交道的DO(data object),效能瓶頸在於對記憶體的使用上(因為位元組都是放在一塊塊的記憶體中),如果能有效的使用記憶體,就能有效地提高DO的效能。ByteChunk和MessageBytes都是Tomcat為了提高處理效率封裝出來的對位元組流和位元組陣列進行優化的類,在執行效率上比java的string和byte陣列要高。這兩個類都都在org.apache.tomcat.util.buf包中。

  3)由於ByteChunk和MessageBytes的使用,Request中欄位的一些耗時操作都會延遲到使用者程式碼一級。也就是說,tomcat內部在使用Request時,都會盡量保證它的欄位處於原始的位元組狀態,直到使用者程式碼(servlet程式碼層)需要時才進行轉換,如果用不到(其實http請求的大部分欄位在我們程式設計時都用不到),就不作轉換。這樣又進一步挖掘出更多的效能潛力,其思想和“延遲載入”的設計模式如出一轍。