Tornado 源碼分析 - 基礎(chǔ)篇

簡述

源碼分析基于 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吐绵,關(guān)于 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() 開始獲取 "調(diào)用堆棧"腻脏。

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ù)的調(diào)用情況。
以上測試方法來自于 參考資料 2击纬。

IOLoop

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

IOLoop 的基本功能

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

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


IOLoop 調(diào)度流程

比較重要的是綠色部分把 listening socket fd 和 connecting socket fd 添加到 ioloop 中的過程更振。然后 ioloop 在監(jiān)聽到這些 fd 可用之后症见,開始調(diào)用對應的處理函數(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,并調(diào)用對應的事件處理函數(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 已經(jīng)通過多路復用幫我們監(jiān)聽了所有 fds村怪,等 fd 可用之后才開始調(diào)用對應的事件處理函數(shù)秽浇。換句話說在調(diào)用 handle_read/handle_accept 我們已經(jīng)知道 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?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市病涨,隨后出現(xiàn)的幾起案子富玷,更是在濱河造成了極大的恐慌,老刑警劉巖没宾,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凌彬,死亡現(xiàn)場離奇詭異沸柔,居然都是意外死亡循衰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門褐澎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來会钝,“玉大人,你說我怎么就攤上這事工三∏ㄋ幔” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵俭正,是天一觀的道長奸鬓。 經(jīng)常有香客問我,道長掸读,這世上最難降的妖魔是什么串远? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮儿惫,結(jié)果婚禮上澡罚,老公的妹妹穿的比我還像新娘。我一直安慰自己肾请,他們只是感情好留搔,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铛铁,像睡著了一般隔显。 火紅的嫁衣襯著肌膚如雪却妨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天荣月,我揣著相機與錄音管呵,去河邊找鬼。 笑死哺窄,一個胖子當著我的面吹牛捐下,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萌业,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼坷襟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了生年?” 一聲冷哼從身側(cè)響起婴程,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抱婉,沒想到半個月后档叔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蒸绩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年衙四,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片患亿。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡传蹈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出步藕,到底是詐尸還是另有隱情惦界,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布咙冗,位于F島的核電站沾歪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雾消。R本人自食惡果不足惜灾搏,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仪或。 院中可真熱鬧确镊,春花似錦、人聲如沸范删。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旨巷,卻和暖如春巨缘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背采呐。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工若锁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斧吐。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓又固,卻偏偏與公主長得像,于是被迫代替她去往敵國和親煤率。 傳聞我的和親對象是個殘疾皇子仰冠,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容