这几天看了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进行封装。
从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实现异步读写,以达到高并发、高性能的目的。