如果你想看 Tomcat 原始碼但又無從入手,不妨從這個專案開始,程式碼量不多,但包含了 Tomcat 的核心處理流程,並且原始碼中有相當豐富的註釋。相信通過此專案你能瞭解:
- NIO 基本程式設計、HTTP 協議的本質、基本的單元測試
- Tomcat 應用部署、自定義類載入器的實現、Servlet 的管理和載入執行以及靜態資源的處理和快取等
- Maven 生成可執行 jar,生成 javadoc,使用 assembly 構建專案,使用 release 外掛釋出到 git等
文末有原始碼地址。本文就 NIO 模型、HTTP 協議解析、Digester 工具以及Servlet 容器這些核心模組的設計和實現的難點以及重點進行簡單介紹。
1. NIO 伺服器的實現
由於非阻塞的特性,NIO 的編寫相比於 BIO 很複雜,而原生 NIO 程式設計就更復雜了,關鍵是處理好通道和處理器的對映,以及各種狀態的管理。此模組的名稱是 rxtomcat-net,結構如下:
主要實現了以下功能:
- Acceptor 使用訊號量對總連線數進行控制
- Acceptor 和 Poller 使用佇列協作完成新連線通道的註冊,因為直接註冊可能會造成死鎖
- Poller 既能通知非阻塞讀寫事件,也能通知模擬阻塞的讀寫事件
- Poller 在通知 I/O 事件時,也會模擬 Tomcat 把通道就緒的事件從關注事件集合中移除
- 利用兩個用於讀和寫的 CountDownLatch 實現模擬阻塞
- Poller 還對通道處理超時,通道超時或關閉時會移除它對應的 Processor,防止記憶體洩露
- 因為是非阻塞,所以當處理中途發現讀或寫的資料不完整,要再次處理時,需要找到原先的處理器,Handler 內部就是使用 ConcurrentHashMap 保持通道和處理器的對映
- NioChannel 是對 SocketChannel 的封裝,主要包含兩個 ByteBuffer 用於讀和寫以及兩個模擬阻塞讀和寫使用的閉鎖,以及提供實際的非阻塞和模擬阻塞讀寫功能
- 為了適配不同協議的處理器定義了一個 Processor 介面
EchoProcessor 是實現的一個回顯處理器,它包含一個 main 方法,可直接執行進行測試。
有一點需要注意,從通道讀取位元組到處理請求都是一個執行緒,只有在非阻塞讀取不完整的請求頭資料時,才有可能切換執行緒。
2. HTTP 協議的解碼和編碼
完整的實現 HTTP 協議是很複雜的,這裡的實現比較簡單,模組的名稱是 rxtomcat-http,結構如下:
主要實現了以下功能:
- 訊息行(請求或響應)的解析和構造,解析時採用有限狀態機的方法,這是非阻塞程式設計常用的手段
- chunked 和 identity 訊息體的解析和構造
- 實現 keepAlive 長連線
- 特殊 URL 解碼,比如 param=%E5%88%9B+a
- 為容器提供底層 Processor 的回撥機制,ActionHook
解析協議麻煩的地方在於處理 TCP 粘包拆包的問題,以及各種緩衝區的清空和重用。在實現時,快取區大部分使用的是 ByteBuffer。
3. 簡單的 Servlet 容器
實現一個簡單的 Servlet 容器,模組的名稱是 rxtomcat-container,結構如下:
簡單起見,只設計了 Context 和 Wrapper 兩個容器,主要實現了以下功能:
- Pipeline 和 Valve 的管道處理模型,以及容器 Lifecycle 生命週期的設計
- DefaultServlet 靜態資源的處理和快取
- 根據 web.xml 部署應用,提取 Servlet 和 Filter 及其配置的對映
- 打破雙親委託的類載入器 Loader,實現從 WEB-INF/classes 和 WEB-INF/lib 載入類,以及 class 檔案熱載入的功能
- 實現 Servlet 的三種 URL 路由規則,以及規範中的 Cookie, HttpSession, FilterChain, HttpServletRequest, HttpServletResponse
- 實現 Session 以及它的管理器 Manager
- 實現了 ServletInputStream 用於支援檔案上傳的處理
這部分的實現稍微繁瑣,也基本復現了 Tomcat 的處理流程,其中唯一有點繞的就是使用 Lifecycle 實現的觀察模式,觸發特定的生命週期事件,使用特定的類來配置和初始化 Context。
4. 其他工具
模組 rxtomcat-utils 主要是一些工具類:
- 簡單實現了 Digester XML 解析工具
- 實現了一個位元組陣列功能類,主要有位元組陣列轉整形,轉十六進位制字串
5. Maven 構建模組
模組 rxtomcat-bootstrap 使用 maven-assembly-plugin 打包釋出二進位制版本,最終構建生成的專案執行目錄結構是:
6. 小結
造輪子確實很費時間,但效果很好。平時寫程式碼,知道原理是什麼,但在編寫時卻無從下手,這就是程式碼寫的少,模仿的少導致的。所以,如果時間充裕,不妨多造造輪子。
- 本文模擬實現的 Tomcat 原始碼地址是:「github.com/tonwu/rxtomcat」
- 使用的版本是 Tomcat 6.0.53,公眾號「頓悟原始碼」後臺回覆關鍵字「Tomcat」可獲取帶有比較詳細中文程式碼註釋的,可直接匯入 Eclipse 執行的 Tomcat 工程。
讀完一個完整的開源專案,實在太費時間了v_v,後續時間充足的話,計劃繼續實現叢集、非同步 Servlet 和 websocket 的程式碼,歡迎 star 關注