Tornado源码阅读

这几天看了Tornado的源码,写这篇文章以做总结。本文采用Tornado v1.2版本的源码,讨论Tornado运行过程而不沉浸入代码实现。

主要模块分析

|---web.py (应用框架层)
|---httpserver.py ( HTTP TCP层 )
|---ioloop.py (数据处理层)
|---iostream.py

web.py 实现了tornado的web框架,定义了Application, RequestHandler两个类。Application是一个单例,注册了全局路由,服务器转发过来的请求调用该类的__call__()。RequestHandler主要功能主要是将handler和url进行映射。

httpserver.py 建立http服务器,并解析http请求。

ioloop.py 主要是将底层的epoll或者说是其他的IO多路复用封装作异步事件来处理。

iostream.py 对sock进行封装。

Tornado运行流程:从请求到响应

从main函数出发:

application = web.Application([
    (r"/", MainPageHandler),
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
ioloop.IOLoop.instance().start()

首先实例化Application(),并设置了路由表。

将application实例传入HTTPServer

    # httpserver.HTTPServer初始化
    def __init__(self, request_callback, no_keep_alive=False...):
       # request_callback 便是传入的application
        self.request_callback = request_callback
        ...

接着http_server监听8080端口,listen()先绑定了端口和地址(self.bind),接着是一系列的判断之后调用self.start()

   # start()函数最重要的一件事就是将self._sockets加入ioloop
    def start(self, num_processes=1):
         if ...
                    ...        
                    self.io_loop = ioloop.IOLoop.instance()
                    # 当有对应请求来时,ioloop将当前线程拉起,  
                    # 执行回调函数 self._handle_events()
                    self.io_loop.add_handler(
                        self._socket.fileno(), self._handle_events,
                        ioloop.IOLoop.READ)
                    return
            os.waitpid(-1, 0)
        else:
            if not self.io_loop:
                self.io_loop = ioloop.IOLoop.instance()
            self.io_loop.add_handler(self._socket.fileno(),
                                     self._handle_events,
                                     ioloop.IOLoop.READ)

接下来看self._handle_events()

    def _handle_events(self, fd, events):
        while True:
             try:
                connection, address = self._socket.accept()
             ...
             try:
                if ...
                  # 将sock封装
                  stream = iostream.IOStream(connection, io_loop=self.io_loop)
                # 实例化HTTPConnection,回调函数self.request_callback
                # 便是application,HTTPConnection实现对请求的解析
                HTTPConnection(stream, address, self.request_callback,
                               self.no_keep_alive, self.xheaders)
            ...

这里需要注意一下HTTPConnection初始化过程

    def __init__(self, stream, address, request_callback, no_keep_alive=False,
                 xheaders=False):
        self.stream = stream
        self.address = address
        self.request_callback = request_callback
        self.no_keep_alive = no_keep_alive
        self.xheaders = xheaders
        self._request = None
        self._request_finished = False
        # Save stack context here, outside of any request.  This keeps
        # contexts from one request from leaking into the next.
        self._header_callback = stack_context.wrap(self._on_headers)
        self.stream.read_until("\r\n\r\n", self._header_callback)

self._on_headers()实现对请求解析出请求头然后组装成HTTPRequest,最后将组装后的请求使用request_callback回调

    def _on_headers(self, data):
        try:
            ...
           # 解析出headers
            headers = httputil.HTTPHeaders.parse(data[eol:])
            self._request = HTTPRequest(
                connection=self, method=method, uri=uri, version=version,
                headers=headers, remote_ip=self.address[0])
            ...
            # 在这里调用了application
            self.request_callback(self._request)
        ...

所以我们便回到了__call__(),看看__call__()干了什么

    def __call__(self, request):
        ...
        # 获取handlers,根据官方文档,如果有多个,取第一个
        handlers = self._get_host_handlers(request)  
        ...
        handler = spec.handler_class(self, request, **spec.kwargs)  # 找到handler
        handler._execute(transforms, *args, **kwargs)  # 执行
        return handler

到了这里,便是用_execute执行handler的业务逻辑,那么便到此结束了吗?答案是否定的,我们还忘了一个最重要的ioloop,其实我们在http_server.start()便使用到ioloop了,下面看看ioloop是怎么轮询的

        while True:
            ...
            try:
                # 这里开始循环轮询,具体干了什么我也不知道
                event_pairs = self._impl.poll(poll_timeout)
           ...
           self._events.update(event_pairs)
           while self._events:
                fd, events = self._events.popitem()
                try:
                    # 这里才是重点,经过前面的铺垫我们有了一个events,接着便是调用对应  
                    # 的handlers,当然我们的这个handlers不是平白无故跳出来的,还记得  
                    # start()最重要的功能是什么吗?
                    self._handlers[fd](fd, events)

到了这里,对于Tornado从一个请求到一个响应的整个过程涉及到的主内容基本过了一遍。在程序初始化阶段,application完成路由表的注册,httpserver建立、绑定端口并开始监听,同时将sockets注册到ioloop中,程序开始后,ioloop不断轮询,当检测到有请求后,通过其文件描述符(sock)找到对应的handlers,此时将执行注册时的回调函数_handle_events(),解析headers,重新封装httprequire后传给application,application根据request找到对应的handler,并执行响应的业务逻辑,之后便是生成相应的response。放图一张(源自:)


总结

从一开始的同步、异步、阻塞、非阻塞、多路复用等基本概念出发,参考上一篇文章,了解了传统网络模型中的进程模型、线程模型,当访问人数越来越多时,传统的网络模型,一个进程或一个线程只能处理一个链接便不再适用。于是提出了多路复用的概念,tornado主要讨论了epoll的使用,同时,tornado适用iostream实现异步读写,以达到高并发、高性能的目的。

相信随着学习的深入,对这篇文章的理解以及修改将一直进行下去。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容