Tornado源码剖析

转自:https://www.tuicool.com/articles/jmuYRfB

Tornado简介

Tornado是Python语言写的Web服务器兼Web框架。Tornado速度快,每秒可处理数以千计的连接,得益于其非阻塞I/O及多路复用的应用。

Tornado核心工作流程

图1.Tornado核心流程

图2.Tornado核心模块UML

Tornado优势

绝大多数的Web服务使用同步网络I/O,基于多线程实现高并发;而Tornado可以在单线程下基于非阻塞的level-triggered模式实现高并发。相比两种模式,后者优势更大。

Tornado在linux下默认使用epoll实现多路I/O复用,相比poll、select,epoll效率更高。

Tornado使用Demo

一个Hello World Demo:

import tornado.ioloop
import tornado.web

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

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

IOLoop模块解析

IOLoop是Tornado的核心,负责服务器的异步非阻塞机制。IOLoop是一个基于level-triggered的I/O事件循环,它使用I/O多路复用模型(select,poll,epoll)监视每个I/O的事件,当指定的事件发生时调用对用的handler处理。

def add_handler(self,fd,handler,events)
        self._handers[fd]=(obj,stack_context.wrap(handler))
        self._impl.register(fd,events | self.ERROR)

IOLoop通过 add_handler 方法将一个文件描述符加入监听,并注册fd的处理器, _impl 为epoll(linux)实例。

IOLoop会在 __main__ 函数中实例化并运行,一个简单的TCPServer例子:

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
sock.setblocking(0)
sock.bind(("",port))
sock.listen(128)
 
io_loop = tornado.ioloop.IOLoop.current()
io_loop.add_handler(sock.fileno(),callback,io_loop.READ)
io_loop.start()

函数 def start(self) 是一个IOLoop异步编程的核心,其实现类似消息队列,I/O事件发生时将事件消息通过 ioloop.add_callback() 加入消息循环。循环体在下一个循环时取出实例中已添加的callback事件并触发:

for callback in callbacks:
        self._run_callback(callback)

除此,循环体通过 event_pairs = _impl.poll(poll_timeout) 获取触发的监听事件,然后调用其处理器就行下一步处理:

fd, events = self._events.popitem() 
fd_obj,handler_func = self._handlers[fd]
handler_func(fd_obj,events)

IOStream模块解析

IOStream模块封装了file-like(file or socket)的一系列非阻塞读写操作。IOStream对file-like的非阻塞读写进行了缓存,提供了读&写Buffer。当读写操作结束时通过callback通知上层调用者从缓存中读写数据。

图3.iostream读写模型

下面是个简单的Demo:

def send_request():
            stream.write(b"GET / HTTP/1.0\r\nHost: friendfeed.com\r\n\r\n")
            stream.read_until(b"\r\n\r\n", on_headers)

def on_headers(data):
            headers = {}
            for line in data.split(b"\r\n"):
                    parts = line.split(b":")
                    if len(parts) == 2:
                            headers[parts[0].strip()] = parts[1].strip()
            stream.read_bytes(int(headers[b"Content-Length"]), on_body)

def on_body(data):
            print(data)
            stream.close()
            tornado.ioloop.IOLoop.current().stop()

if __name__ == '__main__':
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            stream = tornado.iostream.IOStream(s)
            stream.connect(("friendfeed.com", 80), send_request)
            tornado.ioloop.IOLoop.current().start()

上述过程的 stream.connect() 连接成功后触发回调函数 send_request() 发送request请求, send_request() 函数中的 stream.write() 将数据写入iostream的写Buffer,然后内部方法 handle_write() 将Buffer中的数据通过 write_to_fd 写入socket。 read_until() 调用内部方法 _try_inline_read() , _try_inline_read() 调用内部方法 _read_to_buffer_loop 从socket读取消息到Buffer,过程如图3所示,并通过回调函数 on_headers 将Buffer数据取出,函数解析头部信息。

on_headers 读取完毕后通过回调函数 read_bytes 读取Body信息并通过回调函数 on_body 解析Body体。

IOStream的所有I/O事件的回调函数都是通过 _run_callback 方法将回调函数加入到IOLoop的执行列表中,以便在IOLoop的下一次执行时回调。

IOStream的方法 _add_io_state() 监听fd的I/O事件,并在IOLoop中对fd的I/O进行调度,是一种较慢的I/O方式。相比之下直接从IOStream的Buffer中读写是最快的方式。IOStream优先从Buffer中读写数据,当进行READ时,只有当Buffer不可用时,才监听fd的读操作;当进行WRITE时,当 handle_write() 将Buffer中的数据写入到fd,Buffer还尚存数据时,才监听fd的写操作。由于 _add_io_state() 对fd进行I/O监听,所以需要绑定fd的ERROR监听。

fd的实例由IOStream的子类决定。在IOStream子类中,方法 fileno() 返回socket的 fileno() ,则IOLoop对IOStream的监听实则是对IOStream中的socket进行监听。IOStream重写了父类的 read_from_fd 以及 write_to_fd 方法,都是针对内部的socket进行操作。

TCPServer模块

有了ioloop及iostream模块,就很容易构造一个TCPServer。下面是个简单的TCPServer Demo:

server = TCPServer()
server.listen(8888)
IOLoop.current().start()

Tornado的TCPServer模块比较简单,仅有 startstopbindlistenadd_sockets_handle_connectionhandle_stream 方法。

方法 add_sockets 通过 add_accept_handler 将被动socket加入ioloop进行READ监听,当客户端有新的连接建立时调用 accept_handler 事件处理器对连接事件进行处理,返回完成三次握手的新的连接----connection,然后调用回调函数 _handle_connection 对新的连接进行处理。 _hanlde_connection 会实例化一个IOStream,并将新连接传入构造函数。

至此,ioloop,iostream,tcpserver模块已经全部包装完毕,通过 handle_stream 将stream暴露给上层处理(例如HTTP层),子类需要重写 handle_stream 方法来实现具体的stream处理逻辑。

后续

关于HTTPServer的解析将在下一篇Blog讲解

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值