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模块比较简单,仅有 start
, stop
, bind
, listen
, add_sockets
, _handle_connection
, handle_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讲解