tronado 源碼框架分析

一. 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代碼 

 收藏代碼

  1. import socket  
  2.   
  3. def handle_request(client):  
  4.   buf = client.recv(1024)  
  5.   print buf  
  6.   client.send("HTTP/1.1 200 OK\r\n\r\n")  
  7.   client.send("Hello, World")  
  8.   
  9. def main():  
  10.   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  11.   sock.bind(('localhost',8080))  
  12.   sock.listen(5)  
  13.   
  14.   while True:  
  15.     connection, address = sock.accept()  
  16.     handle_request(connection)  
  17.     connection.close()  
  18.   
  19. if __name__ == '__main__':  
  20.   main()  

 

運行以下:

 

六. Hello World from Tornado Http Server

Tornado不能算是一個完整的http服務器, 它只實現小部分的http協議, 大部分要靠用戶去實現.

tornado實際上是一個服務器開發框架, 使用它咱們能夠快速的開發一個高效的http服務器. 下面咱們

就使用tornado再寫一個Hello, World的Http服務器.

 

Python代碼 

 收藏代碼

  1. #!/usr/bin/env python  
  2. # -*- coding:utf-8 -*-  
  3.   
  4. import tornado.httpserver  
  5. import tornado.ioloop  
  6.   
  7. def handle_request(request):  
  8.    message = "Hello World from Tornado Http Server"  
  9.    request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (  
  10.                  len(message), message))  
  11.    request.finish()  
  12.   
  13. http_server = tornado.httpserver.HTTPServer(handle_request)  
  14. http_server.listen(8080)  
  15. tornado.ioloop.IOLoop.instance().start()  

 運行以下:

實現很是簡單, 只須要定義本身的處理方法, 其它的東西所有交給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代碼 

 收藏代碼

  1. for res in socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,  
  2.                             0, socket.AI_PASSIVE | socket.AI_ADDRCONFIG):  
  3.   af, socktype, proto, canonname, sockaddr = res  
  4.     
  5.   # 建立listen socket  
  6.   sock = socket.socket(af, socktype, proto)  
  7.   
  8.   # 設置socket的屬性   
  9.   flags = fcntl.fcntl(sock.fileno(), fcntl.F_GETFD)  
  10.   flags |= fcntl.FD_CLOEXEC  
  11.   fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, flags)  
  12.   sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  
  13.   if af == socket.AF_INET6:  
  14.       if hasattr(socket, "IPPROTO_IPV6"):  
  15.           sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)  
  16.   sock.setblocking(0)  
  17.   
  18.   # bind 和 listen  
  19.   sock.bind(sockaddr)  
  20.   sock.listen(128)  
  21.   
  22.   # 加入ioloop  
  23.   self._sockets[sock.fileno()] = sock  
  24.   if self._started:  
  25.       self.io_loop.add_handler(sock.fileno(), self._handle_events,  
  26.                                ioloop.IOLoop.READ)<span style="white-space: normal;">  
  27. </span>  

這是實現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代碼 

 收藏代碼

  1. def _handle_events(self, fd, events):  
  2.   while True:  
  3.       try:  
  4.           connection, address = self._sockets[fd].accept()  
  5.       except socket.error, e:  
  6.           if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):  
  7.               return  
  8.           raise  
  9.       try:  
  10.           stream = iostream.IOStream(connection, io_loop=self.io_loop)  
  11.           HTTPConnection(stream, address, self.request_callback,  
  12.                          self.no_keep_alive, self.xheaders)  
  13.       except:  
  14.           logging.error("Error in connection callback", exc_info=True)  

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代碼 

 收藏代碼

  1. self.socket = socket  
  2. self.io_loop = io_loop or ioloop.IOLoop.instance()  
  3. self._read_buffer = collections.deque()  
  4. self._write_buffer = collections.deque()  
  5. self.io_loop.add_handler(  
  6.     self.socket.fileno(), self._handle_events, self._state)  

 

 

 

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代碼 

 收藏代碼

  1. #!/usr/bin/env python  
  2. # -*- coding:utf-8 -*-  
  3.   
  4. from tornado import ioloop  
  5. from tornado import iostream  
  6. import socket  
  7.   
  8. def send_request():  
  9.     stream.write("GET /index.html HTTP/1.0\r\nHost: nginx.net\r\n\r\n")  
  10.     stream.read_until("\r\n\r\n", on_headers)  
  11.   
  12. def on_headers(data):  
  13.     headers = {}  
  14.     for line in data.split("\r\n"):  
  15.        parts = line.split(":")  
  16.        if len(parts) == 2:  
  17.            headers[parts[0].strip()] = parts[1].strip()  
  18.     stream.read_bytes(int(headers["Content-Length"]), on_body)  
  19.   
  20. def on_body(data):  
  21.     print data  
  22.     stream.close()  
  23.     ioloop.IOLoop.instance().stop()  
  24.   
  25. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)  
  26. stream = iostream.IOStream(s)  
  27. stream.connect(("nginx.net", 80), send_request)  
  28. ioloop.IOLoop.instance().start()  

首先調用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代碼 

 收藏代碼

  1. def _handle_events(self, fd, events):  
  2.     if not self.socket:  
  3.         logging.warning("Got events for closed stream %d", fd)  
  4.         return  
  5.     try:  
  6.         if events & self.io_loop.READ:  
  7.             self._handle_read()  
  8.         if not self.socket:  
  9.             return  
  10.         if events & self.io_loop.WRITE:  
  11.             if self._connecting:  
  12.                 self._handle_connect()  
  13.             self._handle_write()  
  14.         if not self.socket:  
  15.             return  
  16.         if events & self.io_loop.ERROR:  
  17.             # We may have queued up a user callback in _handle_read or  
  18.             # _handle_write, so don't close the IOStream until those  
  19.             # callbacks have had a chance to run.  
  20.             self.io_loop.add_callback(self.close)  
  21.             return  
  22.         state = self.io_loop.ERROR  
  23.         if self.reading():  
  24.             state |= self.io_loop.READ  
  25.         if self.writing():  
  26.             state |= self.io_loop.WRITE  
  27.         if state != self._state:  
  28.             self._state = state  
  29.             self.io_loop.update_handler(self.socket.fileno(), self._state)  
  30.     except:  
  31.         logging.error("Uncaught exception, closing connection.",  
  32.                       exc_info=True)  
  33.         self.close()  
  34.         raise  

 能夠看到_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代碼 

 收藏代碼

  1. #!/usr/bin/env python  
  2. # -*- coding:utf-8 -*-  
  3.   
  4. from tornado import ioloop  
  5. from tornado import iostream  
  6. import socket  
  7. import errno  
  8. import functools  
  9.   
  10. def handle_connection(client, address):  
  11.   client.send("Hello World from A Simple TCP Server")  
  12.   client.close()  
  13.   
  14. def connection_ready(sock, fd, events):  
  15.     while True:  
  16.         try:  
  17.             connection, address = sock.accept()  
  18.         except socket.error, e:  
  19.             if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):  
  20.                 raise  
  21.             return  
  22.         connection.setblocking(0)  
  23.         handle_connection(connection, address)  
  24.   
  25. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)  
  26. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  
  27. sock.setblocking(0)  
  28. sock.bind(("localhost", 8080))  
  29. sock.listen(128)  
  30.   
  31. io_loop = ioloop.IOLoop.instance()  
  32. callback = functools.partial(connection_ready, sock)  
  33. io_loop.add_handler(sock.fileno(), callback, io_loop.READ)  
  34. io_loop.start()  

建立完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代碼 

 收藏代碼

  1. #!/usr/bin/env python  
  2. # -*- coding:utf-8 -*-  
  3.   
  4. import os  
  5.   
  6. class IOLoop(object):  
  7.     @classmethod  
  8.     def instance(cls):  
  9.         if not hasattr(cls, "_instance"):  
  10.             cls._instance = cls()  
  11.         return cls._instance  
  12.  
  13.     @classmethod  
  14.     def initialized(cls):  
  15.         """Returns true if the singleton instance has been created."""  
  16.         return hasattr(cls, "_instance")  
  17.   
  18.     def service(self):  
  19.       print 'Hello,World'  
  20.   
  21. print IOLoop.initialized(),  
  22. ioloop = IOLoop.instance()  
  23. ioloop.service()  
  24.   
  25. if os.fork() == 0:  
  26.   print IOLoop.initialized(),  
  27.   ioloop = IOLoop.instance()  
  28.   ioloop.service()  

 

代碼直接從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.

相關文章
相關標籤/搜索