一. Tornado是什麼?javascript
Facebook發佈了開源網絡服務器框架Tornado,該平臺基於Facebook剛剛收購的社交聚合網站FriendFeed的實時信息服務開發而來.Tornado由Python編寫,是一款輕量級的Web服務器,同時又是一個開發框架。採用非阻塞I/O模型(epoll),主要是爲了應對高併發 訪問量而被開發出來,尤爲適用於comet應用。html
二. 爲何要閱讀Tornado的源代碼java
Tornado由前google員工開發, 代碼很是精練, 實現也很輕巧, 加上清晰的註釋和豐富的demo, 咱們能夠很容易的閱讀分析tornado. 經過閱讀Tornado的源碼, 你將學到:python
* 理解Tornado的內部實現, 使用tornado進行web開發將更加駕輕就熟ios
* 如何實現一個高性能,非阻塞的http服務器nginx
* 如何實現一個web框架web
* 各類網絡編程的知識, 好比epoll編程
* python編程的絕佳實踐瀏覽器
三. 從http服務器開始服務器
Tornado不只是一個web開發框架, 還本身實現了一個http服務器. 談到http服務器, 咱們天然想到C10K.
其中介紹了不少種服務器的編程模型, tornado的http服務器採用的是:
多進程 + 非阻塞 + epoll + pre-fork 模型
在分析tornado服務器以前, 有必要了解web服務器的工做流程.
四 http服務器工做三部曲
從實現上來講, web服務器是這樣工做的:
(1) 建立listen socket, 在指定的監聽端口, 等待客戶端請求的到來
(2) listen socket接受客戶端的請求, 獲得client socket, 接下來經過client socket與客戶端通訊
(3) 處理客戶端的請求, 首先從client socket讀取http請求的協議頭, 若是是post協議, 還可能要
讀取客戶端上傳的數據, 而後處理請求, 準備好客戶端須要的數據, 經過client socket寫給客戶端
五 Hello World from Http Server
爲了更加理解web服務器的工做流程, 咱們使用python編寫一個簡單的http服務器, 返回Hello, World給瀏覽器
Python代碼
運行以下:
六. Hello World from Tornado Http Server
Tornado不能算是一個完整的http服務器, 它只實現小部分的http協議, 大部分要靠用戶去實現.
tornado實際上是一個服務器開發框架, 使用它咱們能夠快速的開發一個高效的http服務器. 下面咱們
就使用tornado再寫一個Hello, World的Http服務器.
Python代碼
運行以下:
實現很是簡單, 只須要定義本身的處理方法, 其它的東西所有交給Tornado完成. 簡單看一下Tornado作了哪些工做.
首先建立HTTPServer類, 並把咱們的處理方法傳遞過去
而後在8080開始監聽
最後啓動事件循環, 開始監聽網絡事件. 主要是socket的讀和寫
到了這裏, 我有點等不及了, 迫切想了解tornado的內部實現是怎麼樣的. 特別是想知道Tornado的IOLoop究竟是如何
工做的. 接下來咱們開始解剖Tornado
七. Tornado服務器概覽
理解了web服務器的工做流程以後, 咱們再來看看Tornado服務器是如何實現這些處理流程的.
Tornado服務器有3大核心模塊:
(1) IOLoop
與咱們上面那個簡陋的http服務器不一樣, Tornado爲了實現高併發和高性能, 使用了一個
IOLoop來處理socket的讀寫事件, IOLoop基於epoll, 能夠高效的響應網絡事件. 這是Tornado
高效的保證.
(2) IOStream
爲了在處理請求的時候, 實現對socket的異步讀寫, Tornado實現了IOStream類, 用來處理socket
的異步讀寫.
(3) HTTPConnection
這個類用來處理http的請求, 包括讀取http請求頭, 讀取post過來的數據, 調用用戶自定義的處理方法,
以及把響應數據寫給客戶端socket
下面這幅圖描述了tornado服務器的大致處理流程, 接下來咱們將會詳細分析每一步流程的實現
八. 建立listen socket
httpserver.py, 定位到bind方法:
Python代碼
這是實現web服務器的標準步驟, 首先getaddrinfo返回服務器的全部網卡信息, 每塊網卡上都要建立監聽客戶端的請求.
按照socket -> bind -> listen步驟走下來, 最後把新建的listen socket加入ioloop. 那麼ioloop又是個什麼東西呢?
暫時咱們把ioloop理解爲一個事件容器. 用戶把socket和回調函數註冊到容器中, 容器內部會輪詢socket, 一旦某個socket
能夠讀寫, 就調用回調函數來處理socket的讀寫事件.
這裏, 咱們只監聽listen socket的讀事件, 回調函數爲_handle_events, 一旦listen socket可讀, 說明客戶端請求到來,
而後調用_handle_events接受客戶端的請求.
九. accept
httpserver.py, 定位到_handle_events. 這個方法接受客戶端的請求.
爲了便於分析, 我把處理ssl那部分代碼剝離出去了.
Python代碼
accept方法返回客戶端的socket(注意connection的類型是socket), 以及客戶端的地址
而後建立IOStream對象, 用來處理socket的異步讀寫. 這一步會調用ioloop.add_handler把client socket加入ioloop
再而後建立HTTPConnection, 處理用戶的請求.
十. 建立IOStream
10.1 何爲IOStream
accept完成後, 咱們就能夠用client socket與客戶端通訊了. 爲了實現對client socket的異步讀寫, 咱們爲client socket
建立兩個緩衝區: _read_buffer和_write_buffer, 寫: 先寫到_write_buffer, 讀: 從_read_buffer讀. 這樣咱們就不用
直接讀寫socket, 進而實現異步讀寫. 這些操做都封裝在IOStream類中, 歸納來講,
IOStream對socket的讀寫作了一層封裝, 經過使用兩個緩衝區, 實現對socket的異步讀寫.
10.2 IOStream的初始化
IOStream與socket是一一對應的, 初始化主要作4個工做
(1) 初始化IOStream對應的socket
(2) 分配輸入緩衝區_write_buffer
(3) 分配輸出緩衝區_read_buffer
(4) 把socket加入ioloop, 這樣當socket可讀寫的時候, 調用回調函數_handle_events把數據從socket讀入buffer,
或者把數據從buffer發送給socket
找到iosteram.py, 定位到__init__方法
Python代碼
10.3 IOStream提供的接口
IOStream對外提供了3個接口, 用來對socket的讀寫
(1) write(data)
把數據寫入IOStream的_write_buffer
(2) read_until(delimiter, callback)
從_read_buffer讀取數據, delimiter做爲讀取結束符, 完了調用callback
(3) read_bytes(num_of_bytes, callback)
從_read_buffer讀取指定大小的數據, 完了調用callback
read_until和read_bytes都會調用_read_from_buffer把從buffer讀取數據, 而後調用_consume消耗掉buffer中
的數據.
10.4 體驗異步IO
下面咱們來看一個異步IO的實例, 這是一個異步http client的例子, 使用IOStream來下載http://nginx.net/index.html
Python代碼
首先調用connect鏈接服務器, 完成後回調send_request發出請求, 並讀取服務器返回的http協議頭, 而後回調
on_headers解析協議頭, 而後調用read_bytes讀取數據體, 而後回調on_body把數據打印出來. 最後關閉stream
能夠看到, 這一系列的調用都是經過回調函數實現的, 這就是異步的處理方式.
10.5 IOStream響應ioloop事件
上面提到, IOStream初始化的時候, 把socket加入ioloop, 一旦socket可讀寫, 就調用回調函數_handle_events處理IO
事件. 打開iostream.py, 定位到_handle_events
Python代碼
能夠看到_handle_events根據IO事件的類型, 來調用不一樣的處理函數, 對於可讀事件, 調用handle_read來處理.
handle_read會從socket讀取數據, 而後把數據存到_read_buffer.
十一. 處理請求 -- HTTPConnection
HttpConnection類專門用來處理http請求, 處理http請求的通常流程是:
HTTPConnection實現了一系列的函數用來處理這些流程, 參見下圖:
至於每一個函數是如何實現的, 能夠參考代碼
十二. IOLoop
在Tornado服務器中, IOLoop是調度的核心模塊, Tornado服務器回把全部的socket描述符都註冊到IOLoop, 註冊的時候
指明回調處理函數, IOLoop內部不斷的監聽IO事件, 一旦發現某個socket可讀寫, 就調用其註冊時指定的回調函數.
IOLoop的結構圖以下所示:
下面咱們使用IOLoop實現一個簡單的TCP服務器, 看完以後相信能夠對IOLoop有一個大概的瞭解.
12.1 A Simple TCP Server Using IOLoop
Python代碼
建立完listen socket後, 再獲得IOLoop的實例, 後面回介紹IOLoop的單例模式.而後調用add_handle把listen socket
註冊到ioloop中, 指定監聽事件爲READ, 指定回調函數爲connection_ready. 這樣客戶端來了一個鏈接後, 就會調用
connecion_ready來處理鏈接.
12.2 單例模式
看了不少IOLoop的代碼, 有一個地方相信你們注意到了, 獲得IOLoop對象的時候, 都是經過instance()返回的. 事實上,
IOLoop使用了單例模式. 在Tornado運行的整個過程當中, 只有一個IOLoop實例. 僅需一個 IOLoop實例, 就能夠處理所有
的IO事件. 之前學習J2EE的時候接觸過Java的單例模式, 接下來看看Python是如何實現單例模式的.
Python代碼
代碼直接從ioloop.py文件抽取下來的, 演示了Python單例模式的實現方法. 實現至關簡潔, 這得益於python強大的自省
功能. 代碼中使用了cls, 這不是一個關鍵字, 像self同樣, cls是python的一個built-in變量. self表示類的實例, 而cls表示類,
cls通常用於static method, 由於static method無須實例化就能夠調用, 因此傳遞cls給static method. 而後調用cls()
能夠建立對象. 就像調用IOLoop()同樣.
最後兩句話:
Always use 'self' for the first argument to instance methods.
Always use 'cls' for the first argument to class methods.