这几天看了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实现异步读写,以达到高并发、高性能的目的。
相信随着学习的深入,对这篇文章的理解以及修改将一直进行下去。