Tornado是由Facebook开源的一个服务器“套装",适合于做python的web或者使用其本身提供的可扩展的功能,完成了不完整的wsgi协议,可用于做快速的web开发,封装了epoll性能较好。

Tornado 源码主体框架推荐参见: Tornado源码分析之http服务器

文章主要以分析tornado的网络部分即异步事件处理与上层的IOstream类提供的异步IO,其他的模块如web的tornado.web 以后慢慢留作分析。

Tornado 源码 https://github.com/tornadoweb/tornado (github)

 

为什么要阅读Tornado的源码?

Tornado 由前 google 员工开发,代码非常精练,实现也很轻巧,加上清晰的注释和丰富的 demo,我们可以很容易的阅读分析 tornado. 通过阅读 Tornado 的源码,你将学到:

  • 理解 Tornado 的内部实现,使用 tornado 进行 web 开发将更加得心应手。
  • 如何实现一个高性能,非阻塞的 http 服务器
  • 如何实现一个 web 框架。
  • 各种网络编程的知识,比如 epoll
  • python 编程的绝佳实践

 

Tornado模块分类

1. Core web framework

  • tornado.web — 包含web框架的大部分主要功能,包含RequestHandler和Application两个重要的类
  • tornado.httpserver — 一个无阻塞HTTP服务器的实现
  • tornado.template — 模版系统
  • tornado.escape — HTML,JSON,URLs等的编码解码和一些字符串操作
  • tornado.locale — 国际化支持

2. Asynchronous networking 底层模块

  • tornado.ioloop — 核心的I/O循环
  • tornado.iostream — 对非阻塞式的 socket 的简单封装,以方便常用读写操作
  • tornado.httpclient — 一个无阻塞的HTTP服务器实现
  • tornado.netutil — 一些网络应用的实现,主要实现TCPServer类

3. Integration with other services

  • tornado.auth — 使用OpenId和OAuth进行第三方登录
  • tornado.database — 简单的MySQL服务端封装
  • tornado.platform.twisted — 在Tornado上运行为Twisted实现的代码
  • tornado.websocket — 实现和浏览器的双向通信
  • tornado.wsgi — 与其他python网络框架/服务器的相互操作

4. Utilities

  • tornado.autoreload — 生产环境中自动检查代码更新
  • tornado.gen — 一个基于生成器的接口,使用该模块保证代码异步运行
  • tornado.httputil — 分析HTTP请求内容
  • tornado.options — 解析终端参数
  • tornado.process — 多进程实现的封装
  • tornado.stack_context — 用于异步环境中对回调函数的上下文保存、异常处理
  • tornado.testing — 单元测试

 

首先说明几点问题:

(1)文章供大家交流使用,如果有错误,发扬开源精神,共同交流

(2)文章不加以说明,均以Linux环境为例

(3)如果有epoll,tornado默认使用epoll,这里就不分析select和KQueue了

(4)请不要再问为什么使用python来作为网络库而不是高效的C/C++了,因为对于IO密集型程序来说,上层语言的程序运行差异没有那么大,而且tornado中使用的epoll部分也是用C来写的。

 

下面开始我们的tornado之旅,看源代码之前必定需要有一份源码了,大家可以去官网下载一份。

一.源码组织:

  |---__init__.py

   ---auth.py

   ---......

   ---epoll.c

   ---ioloop.py

   ---iostream.py

   ---...

tornado网络部分最核心的两个模块就是 ioloop.py与iostream.py,我们主要分析的就是这两个部分。

ioloop.py 主要的是将底层的epoll或者说是其他的IO多路复用封装作异步事件来处理

iostream.py主要是对于下层的异步事件的进一步封装,为其封装了更上一层的buffer(IO)事件

很好的一点就是在tornado的源码中,都提供了一个简单的Demo,我就以这些Demo为例来讲,再多的话也不如看代码,程序猿最好的交流方式就是看代码。

tornado/ioloop.py 源码:

        import errno
        import functools
        import ioloop
        import socket
 
        def connection_ready(sock, fd, events):
            while True:
                try:
                    connection, address = sock.accept()
                except socket.error, e:
                    if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                        raise
                    return
                connection.setblocking(0)
                handle_connection(connection, address)
 
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.setblocking(0)
        sock.bind(("", port))
        sock.listen(128)
 
#创建一个ioloop 实例
        io_loop = ioloop.IOLoop.instance()
#冻结 connection_ready 的第一个参数为 sock ,既 socket 的返回值
        callback = functools.partial(connection_ready, sock)
#注册函数,  第一个参数是将 sock 转换为标准的描述符,第二个为回调函数,第三个是事件类型       
        io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
#开始~
        io_loop.start()

可以看到在注释前都是使用了传统的创建服务器的方式,不用多介绍,注意就是把套接口设置为非阻塞方式

创建ioloop实例,这里是使用了ioloop.IOLoop中的 instance()静态方法,以 @classmethod 方式包装

    @classmethod  
    def instance(cls):  
        if not hasattr(cls, “_instance”):  
            cls._instance = cls()  
        return cls._instance  

    @staticmethod
    def instance():
        """Returns a global `IOLoop` instance.

        Most applications have a single, global `IOLoop` running on the
        main thread.  Use this method to get this instance from
        another thread.  To get the current thread's `IOLoop`, use `current()`.
        """
        if not hasattr(IOLoop, "_instance"):
            with IOLoop._instance_lock:
                if not hasattr(IOLoop, "_instance"):
                    # New instance after double check
                    IOLoop._instance = IOLoop()
        return IOLoop._instance

上面静态方法2中,是为了防止多线程,因此加锁了

    # Global lock for creating global IOLoop instance
    _instance_lock = threading.Lock()

 

继续讲解代码,在后面的add_handler中,程序为我们的监听套接口注册了一个回调函数和一个事件类型

工作方式是这样,在注册了相应的事件类型(io_loop.READ)和回调函数(callback)以后,程序开始启动,如果在相应的套接口上有事件发生(注册的事件类型,如 io_loop.READ)那么调用相应的回调函数。

上面代码通俗易懂,当监听套接口有可读事件(io_loop.READ)发生,意味着来了一个新连接,在回调函数中就可以对这个套接口accept,并调用相应的处理函数,其实应该是处理函数也设置为异步的,将相应的连接套接口也加入到事件循环并注册相应的回调函数,只是这里没有展示出来。

在使用非阻塞方式的accept时候常常返回EAGAIN,EWOULDBLOCK 错误,这里采取的方式是放弃这个连接。

 

原文破修电脑的