Tornado 源碼分析 - 基礎篇

簡述

源碼分析基于 Tornado 2.0 版本,2.0 版本時候 Tornado 的介紹還是:

Tornado is an open source version of the scalable, non-blocking web server and and tools that power FriendFeed.

這個時候介紹中還未加入 asynchronous networking library,關于 Tornado 在以后版本中對 asynchronous 的支持,在下篇文章會進行介紹。

這篇文章主要介紹 Tornado 基本執(zhí)行流程,對 HTTP Request 的解析和處理,和 No-blocking I/O 在 Tornado 的具體應用。

執(zhí)行流程

我們用一個簡單的 Hello World 示例分析其基本的執(zhí)行流程。

#!/usr/bin/env python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")


def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()

代碼測試無誤后,添加 pdb.set_trace() 開始獲取 "調用堆棧"。

class TestHandler(RequestHandler):
    def get(self):
        import pdb
        pdb.set_trace()
        self.write("Hello, World!\n")

使用 curl 請求 curl http://localhost:8888 觸發(fā)斷點。
pdb 中輸入 w 獲取詳細堆棧信息,輸入 u d 可在上下堆棧之間切換,查看函數(shù)的調用情況。
以上測試方法來自于 參考資料 2。

IOLoop

先來看一下 IOLoop 的基本功能:

IOLoop 的基本功能

可以看到 IOLoop 是典型的 Reactor 模型 的實現(xiàn)。

再來看一下 Hello World 那個代碼的詳細執(zhí)行流程,以及 IOLoop 是如何發(fā)揮總調度作用的:


IOLoop 調度流程

比較重要的是綠色部分把 listening socket fd 和 connecting socket fd 添加到 ioloop 中的過程。然后 ioloop 在監(jiān)聽到這些 fd 可用之后,開始調用對應的處理函數(shù)開始處理。

HTTP Request 的解析和處理

HTTP Request 的解析和處理

數(shù)據(jù)的讀寫都是通過 IOStream 實現(xiàn),綠色部分表示對 HTTP Request Header 和 HTTP Request Body 的處理。

為加深對 IOLoop 的理解,我們來實現(xiàn)一個簡單的 EchoServer。

def handle_accept(fd, events):
    connection, address = fd_sockets[fd].accept()
    fd_sockets[connection.fileno()] = connection

    io_loop = tornado.ioloop.IOLoop.instance()
    io_loop.add_handler(connection.fileno(), handle_read, io_loop.READ)


def handle_read(fd, events):
    data = fd_sockets[fd].recv(1024)
    if data:
        fd_sockets[fd].sendall(data)
    else:
        io_loop = tornado.ioloop.IOLoop.instance()
        io_loop.remove_handler(fd)
        fd_sockets[fd].close()


def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(5)
    fd_sockets[s.fileno()] = s

    io_loop = tornado.ioloop.IOLoop.instance()
    io_loop.add_handler(s.fileno(), handle_accept, io_loop.READ)
    io_loop.start()

各 Class 的主要作用

Tornado 各個類的分工還是很清晰的,實現(xiàn)也比較容易看懂。

  • HTTPServer

    處理套接字的 listen/bind/accept。

  • IOStream

    處理套接字的 read/write。

  • HTTPConnection

    處理與 HTTP client 建立的連接,解析 HTTP Request 的 header 和 body。

  • IOLoop

    I/O loop,循環(huán)取出可用的 fd,并調用對應的事件處理函數(shù)。

  • RequestHandler

    處理請求,支持 GET/POST 等操作。

No-bloking I/O

先看兩個事件處理函數(shù):

def handle_read():
    socket.setblocking(False)
    while True:
        try:
            data = socket.recv(1024)
        except socket.error, e:
            if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
                return
            raise


def handle_accept():
    socket.setblocking(False)
    while True:
        try:
            connection, address = sockets.accept()
        except socket.error, e:
            if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
                return
            raise

考慮一下這里為什么要采用非阻塞方式來 read/accept 數(shù)據(jù)?
我們發(fā)現(xiàn) IOLoop 已經通過多路復用幫我們監(jiān)聽了所有 fds,等 fd 可用之后才開始調用對應的事件處理函數(shù)。換句話說在調用 handle_read/handle_accept 我們已經知道 fd 可用了,那為什么不采用阻塞 I/O 讀取,而要采用非阻塞 I/O 的方式呢?
問題其實可以抽象成「多路復用為什么要搭配非阻塞 I/O」,具體討論可以參考 這里。

參考資料

  1. Tornado 2.0
  2. 有沒有什么很好的 Tornado 的教材或者開源項目可以做參考的?
  3. Tornado: 1. 流程分析
  4. Tornado: 2. 源碼分析
  5. 高性能網(wǎng)絡編程(一)---- accept 建立連接
  6. 為什么 IO 多路復用要搭配非阻塞 IO?
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容