5.Tornado HTTP服務器的基本流程---Tornado底層I/O的內部實現

本小節介紹Tornado HTTP服務器的基本流程,分別分析httpserver, ioloop, iostream模塊的代碼來剖析Tornado底層I/O的內部實現。

httpserver.py中給出了一個簡單的http服務器的demo,代碼如下所示:

01 from tornado import httpserver
02 from tornado import ioloop
03   
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))
08    request.finish()
09   
10 http_server = httpserver.HTTPServer(handle_request)
11 http_server.bind(8888)
12 http_server.start()
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):
7     def stop(self):
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)
04  
05 def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128):
06     # 省略sockets創建,address,flags處理部分代碼
07     for res in set(socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,
08                                   0, flags)):
09         af, socktype, proto, canonname, sockaddr = res
10         # 創建socket
11         sock = socket.socket(af, socktype, proto)
12         # 設置socket屬性,代碼省略
13          
14         sock.bind(sockaddr)
15         sock.listen(backlog)
16         sockets.append(sock)
17     return sockets
18  
19 def add_sockets(self, sockets):
20     if self.io_loop is None:
21         self.io_loop = IOLoop.instance()
22  
23     for sock in sockets:
24         self._sockets[sock.fileno()] = sock
25         add_accept_handler(sock, self._handle_connection,
26                            io_loop=self.io_loop)
27                             
28 def add_accept_handler(sock, callback, io_loop=None):
29     if io_loop is None:
30         io_loop = IOLoop.instance()
31  
32     def accept_handler(fd, events):
33         while True:
34             try:
35                 connection, address = sock.accept()
36             except socket.error, e:
37                 if e.args[0in (errno.EWOULDBLOCK, errno.EAGAIN):
38                     return
39                 raise
40             # 當有連接被accepted時callback會被調用
41             callback(connection, address)
42     io_loop.add_handler(sock.fileno(), accept_handler, IOLoop.READ)
43      
44 def _handle_connection(self, connection, address):
45     # SSL部分省略
46     try:
47         stream = IOStream(connection, io_loop=self.io_loop)
48         self.handle_stream(stream, address)
49     except Exception:
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,
02                  xheaders=False):
03     self.request_callback = request_callback
04     # some configuration code
05     self._header_callback = stack_context.wrap(self._on_headers)
06     self.stream.read_until(b("\r\n\r\n"), self._header_callback)
07  
08 def _on_headers(self, data):
09     # some codes
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進程管理工具的安裝使用

發佈了13 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章