《How Tomcat Works》讀書筆記(三):Tomcat default connector

Chapter 4: Tomcat default connector

何爲default Connector?其實這裏指的是tomcat最初設計時使用的Connector,儘管問題多多,現在已經被coyote所取代,但作爲教學用例,default Connector仍然不失爲一個優秀的組件,值得一學!

這一章的目的是系統的講述tomcat的Connector,同時爲介紹後面的容器作鋪墊。

從這一章開始,內容越來越多也逐步深化,因此做筆記也只能摘錄一部分,沒有基礎的人看了也許會覺得不連貫,那麼推薦你先去看看原書或者它的翻譯。

Http 1.1

default Connector支持Http 1.1標準,相比Http1.0,1.1最大的特色在於增加了對長連接的支持。舉個例子,我們瀏覽網頁的時候,一個html頁面中,其實包含了很多資源, 如圖像、視頻、聲音,等等。每個資源都有獨立的URL地址,在過去1.0時,一次只能針對一個URL獲取資源,因此往往打開一個頁面的操作其實包含了很多 次的“客戶端/服務器”之間的連接過程。反反覆覆的建立、斷開連接是非常耗時的,而且也無疑增加了服務器的負擔。在Http1.1中,通過長連接,可以一 次性獲取多個資源,無疑是大大節約了網絡資源。

客戶端可以通過在請求中包含“connection: keep-alive”這一頭字段來要求服務器提供長連接

Chunked Encoding(塊編碼)

有了長連接,客戶端應該如何區分在一個連接中傳輸的多個資源呢?在過去的1.0中,由於每次只傳輸一個資源,因此不會有這個問題。能否使用 content-length字段?不行。因爲服務器並不會等到一整個圖像文件都準備好了才傳給你,往往是一次傳若干字節,這種做法相當於操作系統的“分 時”,有利於提高併發性和用戶的使用感受。專家們最後終於想出一個辦法,就是在每次傳輸時加上一些信息表示這次要傳多少字節,例如:

1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n

通過“十六進制數字”+“\r\n”,代表一次傳輸的字節數,最後的“0\r\n”表示一次會話的結束。但書上並沒有說一次會話就相當於傳一個資源,因此到底如何區分多個資源,還不是很清楚,需要研究Http協議才能知道答案。

Connector接口

二話不說,上圖:

image

tomcat裏面最基本的兩個接口應該就是Connector和container了,接口本身很簡單很抽象,但衍生出來的東西卻非常複雜。

從圖中可以看出,Connector和container是一一對應的,Connector有setContainer方法,但container 沒有類似的setConnector,說明只有Connector才知道container的存在,反之是不成立的。Connector是爲了把 Request和Response傳遞給container;而container不必理會誰給他Request,只要能拿到Request、再把響應寫 入Response就行了

此外,一個HttpConnector中包含了多個HttpProcessor,目的是爲了支持多線程,可以同時處理多個Socket連接。

多線程處理Socket

old version

先來回顧之前的實現:

while (!stopped) {
       Socket socket = null;
       try {
         socket = serversocket.accept();
       }        catch (Exception e) {
         continue;
       }
       // Hand this socket off to an Httpprocessor
       HttpProcessor processor = new Httpprocessor(this);
       processor.process(socket);
     }

這種做法的後果是,整個處理流程完全是同步的,因爲 processor.process 方法是同步的。這樣只有一個請求完全處理完畢後,processor.process 纔會返回,while循環才能繼續,效率之低可想而知,根本應付不了幾個併發請求。

new version

既然找到了問題所在,接下來就要提出解決方案。很自然的想法是:線程。

大致思路是這樣的:通過一個堆棧,我們保存若干數量的HttpProcessor,這些HttpProcessor都實現了Runnable接口。 HttpConnector和HttpProcessor分別代表了生產者和消費者。一方面,HttpConnector獲得一個個的Socket,放入 空閒的HttpProcessor中,HttpProcessor處理完畢後,通過recycle方法將自己“回收”。如果沒有空閒的 HttpProcessor,則HttpConnector會將該Socket丟棄,直接關閉。

說到生產者和消費者這種同步模型,那麼控制同步的“關鍵域”是什麼呢?此處其實是一個布爾變量available,表示此時 HttpProcessor是否空閒。HttpConnector調用HttpProcessor的同步的assign方法,將Socket交給 HttpProcessor,如果available爲假,那麼assign方法調用完成直接返回,如果爲真,則HttpConnector就wait在 那裏直到可用爲止。HttpProcessor本身則採取相反的策略。

any question?

仔細推敲一下,會覺得這裏的算法有些奇怪。首先,HttpProcessor是放在堆棧中的,凡是“非空閒”的HttpProcessor都不在堆 棧中,它自己忙完了纔會recycle回去,所以HttpConnector從堆棧中取出、並調用assign時的HttpProcessor肯定是“空 閒”的;其次,讓HttpConnector專門“wait”在那裏,等於是整個tomcat都卡在那裏,無法再接受新的Socket,不合情理。希望能 有高人指點一下其中的原因,到底是bug,還是有更深層次的考慮。

Request & Response

Request的主要功能基本沒有變,Facade依然存在,但是其繼承關係比原來複雜了許多,書中也沒有一一解釋,讓人云裏霧裏,或許後面的章節會有補充。

image

在HttpProcessor取得新的Socket後,依舊是通過process方法對Socket進行處理,流程上還是按照之前的做法,依次解析

  1. Connection:檢查有沒有使用代理
  2. Request:類似第三章
  3. Headers:類似第三章,但是用char[]取代String作爲頭字段的名稱,例如:static final char[] AUTHORIZATION_NAME =  "authorization".toCharArray();
發佈了33 篇原創文章 · 獲贊 0 · 訪問量 1877
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章