在Tornado框架上发生的故事,起源于IOLoop。IOLoop就像是Tornado的心脏,他接受来自Browser,爬虫,蜘蛛发出的链接,接收他们的数据,然后按Http协议进行解析,将解析的结果,经由url路由,分配到不同的RequestHandler进行处理,RequestHandler处理好结果,再由IOLoop发往客户端。
IOLoop位于ioloop.py模块,扒开它的源码,发现文件有600多行,其实大部分是注释(作者的编码非常好,看代码非常舒服),真正代码可能不到200行。IOLoop类是该模块中最重要的类,而start()方法是该类重要的方法,用于启动ioloop接收来自客户端的链接,把这个方法的主要代码提出来简单说明一下,就知道他的基本工作原理:
- def start(self):
- while True:
- # ...
- try:
- # self._impl是一个poll对象,tornado在linux上,是epoll,在macos上的kqueue,其他平台上也有相关的封装,下文有说明
- event_pairs = self._impl.poll(poll_timeout)
- except Exception, e:
- # ...
- self._events.update(event_pairs) # 这里才是“心脏”。把从poll中获取到的IO事件添加到 _events列表中
- while self._events:
- fd, events = self._events.popitem()
- try:
- # 从 _events列表中获取一个IO事件,并触发回调
- # self._handlers是一个字典,key为socket的 fileno,value为一个函数类似handler(fileno, events)的回调函数
- self._handlers[fd](fd, events)
- except (OSError, IOError), e:
- #...
- except Exception:
- #...
- # 注册/取消 IO事件的代码
- def add_handler(self, fd, handler, events):
- """注册IO事件"""
- self._handlers[fd] = stack_context.wrap(handler)
- self._impl.register(fd, events | self.ERROR)
- def update_handler(self, fd, events):
- """更新IO事件"""
- self._impl.modify(fd, events | self.ERROR)
- def remove_handler(self, fd):
- """删除指定文件描述符的IO事件"""
- self._handlers.pop(fd, None)
- self._events.pop(fd, None)
- try:
- self._impl.unregister(fd)
- except (OSError, IOError):
- logging.debug("Error deleting fd from IOLoop", exc_info=True)
start()方法大概是这样工作的:从poll对象获取IO事件,然后将IO事件提交给相应的handler处理。上面代码略去了处理callback, timeout的逻辑,代码不难,感兴趣的同学自己扒代码来看。说白了,IOLoop其实就是一个reactor
关于poll
ioloop模块使用poll对象,在不同的平台上实现是不同的,在linux下就是epoll,macos下为kqueue,而在windows下,为select,如下:
TCPServer与HttpServer
TCPServer在netutil模块中定义,内容通过ioloop的实现网络连接、通信处理。看下它的构造函数:
- def __init__(self, io_loop=None, ssl_options=None):
- self.io_loop = io_loop # IOLoop实例引用
- self.ssl_options = ssl_options
- self._sockets = {} # fd -> socket object
- self._pending_sockets = []
- self._started = False
TCPServer中,比较重要的方法,看一下add_sockets,如下:
- def add_sockets(self, sockets):
- if self.io_loop is None:
- self.io_loop = IOLoop.instance()
- for sock in sockets:
- self._sockets[sock.fileno()] = sock
- # 向poll注册IO事件,并设置好回调函数。这里把实例方法_handle_connection作为回调。该方法在HttpServer中被重写
- add_accept_handler(sock, self._handle_connection,
- io_loop=self.io_loop)
- def add_accept_handler(sock, callback, io_loop=None):
- if io_loop is None:
- io_loop = IOLoop.instance()
- def accept_handler(fd, events):
- while True:
- try:
- connection, address = sock.accept() # 接收新链接
- except socket.error, e:
- if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
- return
- raise
- callback(connection, address)
- # 向ioloop注册IO事件
- io_loop.add_handler(sock.fileno(), accept_handler, IOLoop.READ)
HTTPServer在httpserver模块中,继承了TCPServer,它的代码很简单,如下:
- class HTTPServer(TCPServer):
- def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
- xheaders=False, ssl_options=None, **kwargs):
- self.request_callback = request_callback # tornado.web.Application 对象,下节分析
- self.no_keep_alive = no_keep_alive
- self.xheaders = xheaders
- TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
- **kwargs)
- def handle_stream(self, stream, address):
- """tcpserver接收到链接后的回调"""
- HTTPConnection(stream, address, self.request_callback,
- self.no_keep_alive, self.xheaders)
上面的代码可以看到,HTTPServer只重写了handle_stream方法,而handle_stream在TCPServer中是作为接收到链接时的回调,这里handle_stream只实例了一个HttpConnection对象,所以接下来事件,与HTTPConnection相关。后面很精彩,下文待续。