本小節介紹Tornado HTTP服務器的基本流程,分別分析httpserver,
ioloop, iostream模塊的代碼來剖析Tornado底層I/O的內部實現。
httpserver.py中給出了一個簡單的http服務器的demo,代碼如下所示:
01 |
from tornado import httpserver |
02 |
from tornado import ioloop |
04 |
def handle_request(request): |
05 |
message = "You
requested %s\n" % request.uri |
06 |
request.write( "HTTP/1.1
200 OK\r\nContent-Length: %d\r\n\r\n%s" % ( |
07 |
len (message),
message)) |
10 |
http_server = httpserver.HTTPServer(handle_request) |
11 |
http_server.bind( 8888 ) |
13 |
ioloop.IOLoop.instance().start() |
該http服務器主要使用到IOLoop, IOStream, HTTPServer, HTTPConnection幾大模塊,分別在代碼ioloop.py, iostream.py, httpserver.py中實現。工作的流程如下圖所示:
服務器的工作流程:首先按照socket->bind->listen順序創建listen socket監聽客戶端,並將每個listen socket的fd註冊到IOLoop的單例實例中;當listen socket可讀時回調_handle_events處理客戶端請求;在與客戶端通信的過程中使用IOStream封裝了讀、寫緩衝區,實現與客戶端的異步讀寫。
HTTPServer分析
HTTPServer在httpserver.py中實現,繼承自TCPServer(netutil.py中實現),是一個無阻塞、單線程HTTP服務器。支持HTTP/1.1協議keep-alive連接,但不支持chunked encoding。服務器支持'X-Real-IP'和'X-Scheme'頭以及SSL傳輸,支持多進程爲prefork模式實現。在源代碼的註釋中對以上描述比較詳細的說明,這裏就不再細說。
HTTPServer和TCPServer的類結構:
1 |
class HTTPServer(TCPServer): |
2 |
def __init__( self ,
request_callback, no_keep_alive = False ,
io_loop = None ,
xheaders = False ,
ssl_options = None , * * kwargs): |
3 |
def handle_stream( self ,
stream, address): |
1 |
class TCPServer( object ): |
2 |
def __init__( self ,
io_loop = None ,
ssl_options = None ): |
3 |
def listen( self ,
port, address = ""): |
4 |
def add_sockets( self ,
sockets): |
5 |
def bind( self ,
port, address = None ,
family = socket.AF_UNSPEC,
backlog = 128 ): |
6 |
def start( self ,
num_processes = 1 ): |
8 |
def handle_stream( self ,
stream, address): |
9 |
def _handle_connection( self ,
connection, address): |
文章開始部分創建HTTPServer的過程:首先需要定義處理request的回調函數,在tornado中通常使用tornado.web.Application封裝。然後構造HTTPServer實例,註冊回調函數。接下來監聽端口,啓動服務器。最後啓動IOLoop。
01 |
def listen( self ,
port, address = ""): |
02 |
sockets = bind_sockets(port,
address = address) |
03 |
self .add_sockets(sockets) |
05 |
def bind_sockets(port,
address = None ,
family = socket.AF_UNSPEC,
backlog = 128 ): |
07 |
for res in set (socket.getaddrinfo(address,
port, family, socket.SOCK_STREAM, |
09 |
af,
socktype, proto, canonname, sockaddr = res |
11 |
sock = socket.socket(af,
socktype, proto) |
19 |
def add_sockets( self ,
sockets): |
20 |
if self .io_loop is None : |
21 |
self .io_loop = IOLoop.instance() |
24 |
self ._sockets[sock.fileno()] = sock |
25 |
add_accept_handler(sock, self ._handle_connection, |
28 |
def add_accept_handler(sock,
callback, io_loop = None ): |
30 |
io_loop = IOLoop.instance() |
32 |
def accept_handler(fd,
events): |
35 |
connection,
address = sock.accept() |
36 |
except socket.error,
e: |
37 |
if e.args[ 0 ] in (errno.EWOULDBLOCK,
errno.EAGAIN): |
41 |
callback(connection,
address) |
42 |
io_loop.add_handler(sock.fileno(),
accept_handler, IOLoop.READ) |
44 |
def _handle_connection( self ,
connection, address): |
47 |
stream = IOStream(connection,
io_loop = self .io_loop) |
48 |
self .handle_stream(stream,
address) |
50 |
logging.error( "Error
in connection callback" ,
exc_info = True ) |
這裏分析HTTPServer通過listen函數啓動監聽,這種方法是單進程模式。另外可以通過先後調用bind和start(num_processes=1)函數啓動監聽同時創建多進程服務器實例,後文有關於此的詳細描述。
bind_sockets在啓動監聽端口過程中調用,getaddrinfo返回服務器的所有網卡信息, 每塊網卡上都要創建監聽客戶端的請求並返回創建的sockets。創建socket過程中綁定地址和端口,同時設置了fcntl.FD_CLOEXEC(創建子進程時關閉打開的socket)和socket.SO_REUSEADDR(保證某一socket關閉後立即釋放端口,實現端口複用)標誌位。sock.listen(backlog=128)默認設定等待被處理的連接最大個數爲128。
返回的每一個socket都加入到IOLoop中同時添加回調函數_handle_connection,IOLoop添加對相應socket的IOLoop.READ事件監聽。_handle_connection在接受客戶端的連接處理結束之後會被調用,調用時傳入連接和ioloop對象初始化IOStream對象,用於對客戶端的異步讀寫;然後調用handle_stream,傳入創建的IOStream對象初始化一個HTTPConnection對象,HTTPConnection封裝了IOStream的一些操作,用於處理HTTPRequest並返回。至此HTTP
Server的創建、啓動、註冊回調函數的過程分析結束。
HTTPConnection分析
該類用於處理http請求。在HTTPConnection初始化時對self.request_callback賦值爲一個可調用的對象(該對象用於對http請求的具體處理和應答)。該類首先讀取http請求中header的結束符b("\r\n\r\n"),然後回調self._on_headers函數。request_callback的相關實現在以後的系列中有詳細介紹。
01 |
def __init__( self ,
stream, address, request_callback, no_keep_alive = False , |
03 |
self .request_callback = request_callback |
05 |
self ._header_callback = stack_context.wrap( self ._on_headers) |
06 |
self .stream.read_until(b( "\r\n\r\n" ), self ._header_callback) |
08 |
def _on_headers( self ,
data): |
10 |
self .request_callback( self ._request) |
多進程HTTPServer
程模式,同時提供了創建多進程服務器的接口,具體實現是在主進程啓動HTTPServer時通過process.fork_processes(num_processes)產生新的服務器子進程,所有進程之間共享端口。fork_process的方法在process.py中實現,十分簡潔。對fork_process詳細的分析,可以參考 21.番外篇:Tornado的多進程管理分析。番外篇:Tornado的多進程管理分析
FriendFeed使用nginx提供負載均衡、反向代理服務並作爲靜態文件服務器,在後端服務器上可以部署多個Tornado實例。一般可以通過Supervisor控制Tornado app,然後再通過nginx對Tornado的輸出進行反向代理。 具體可以參考下這篇文章: Supervisord進程管理工具的安裝使用。