Tornado
简介
Tornado龙卷风是一个开源的网络服务器框架,它是基于社交聚合网站FriendFeed的实时信息服务开发而来的。2007年由4名Google前软件工程师一起创办了FriendFeed,旨在使用户能够方便地跟踪好友在Facebook和Twitter等多个社交网站上的活动。结果两年后,Facebook宣布收购FriendFeed,交易价格约为5000万美元。而此时FriendFeed只有12名员工。据说这帮人后来又到了Google,搞出了现在的Google App Engine...
我们开发这个Web服务器的主要目的就是为了处理FriendFeed的实时功能 -- 在FriendFeed的应用里每个活动用户都会保持着一个服务器连接。
区别
Tornado与现代主流的Web服务器框架有着明显的区别:它使非阻塞式的服务器,速度相当快。这得益于其非阻塞的方式和对epoll的运用。Tornado每秒可以处理数以千计的连接,对于实时Web服务来说Tornado确实是一个理想的Web框架。
与Node.js相同的是,Tornado也采用的是单进程单线程异步IO的网络模型,它们都可以编写异步非阻塞的程序。但由于Node.js是Google Chrome V8引擎的JS运行环境或工具包,它属于偏底层的抽象,扩展了JS编写服务器程序的能力,所以基于Node.js会由不同的Web框架。从这个角度来看Node.js和Tornado其实并不在一个层次上。
Tornado是使用Python编写的Web服务器兼Web应用框架,与主流Web服务器框架不同的是,Tornado是异步非阻塞式服务器,得益于非阻塞式和对epoll
模型的运用,Tornado是实时Web服务的一个理想框架,它非常适合开发长轮询、WebSocket和需要与每个用户建立持久连接的应用。
特点
- 轻量级Web框架
- 异步非阻塞IO处理方式
Tornado采用的单进程单线程异步IO的网络模式,其高性能源于Tornado基于Linux的Epoll(UNIX为kqueue)的异步网络IO。 - 出色的抗负载能力
- 不依赖多进程或多线程
- WSGI全栈替代产品
WSGI把应用(Application)和服务器(Server)结合起来,Tornado既可以是WSGI应用也可以是WSGI服务。 - 既是WebServer也是WebFramework
Tornado是基于Bret Taylor和其他人员为FrientFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不同于那些最多只能达到1w并发连接的传统网络服务器。Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使其成为一个拥有高性能的框架。
结构
- Web框架
主要包括RequestHandler
用于创建Web应用程序和各种支持类的子类 - HTTP服务器与客户端
主要包括HTTPServer
和AsyncHTTPClient
- 异步网络库
主要包括IOLoop
和IOStream
作为HTTP组件的构建块 - 协程库
Tornado的Web框架和HTTP服务器一起提供了完整的堆栈替代方案WSGI
模块
Tornado是一个轻量级框架,它的模块不多最重要的模块是web,web模块包含了Tornado大部分主要功能的Web框架,其他模块都是工具性质的,以便让Web模块更加有用。
- Core Web Framework 核心Web框架
-
tornado.web
包括Web框架大部分主要功能,包括RequestHandler
和Application
类。 -
tornado.httpserver
一个无阻塞HTTP服务器的实现 -
tornado.template
模板系统 -
tornado.escape
HTML、JSON、URLs等编码解码和字符串操作 -
tornado.locale
国际化支持
- Asynchronous Networking 异步网络底层模块
-
tornado.ioloop
核心IO循环 -
tornado.iostream
对非阻塞的Socket的简单封装以方便常用读写操作 -
tornado.httpclient
无阻塞的HTTP服务器实现 -
tornado.netutil
网络应用的实现主要是TCPServer类
- Integration With Other Services 系统集成服务
-
tornado.auth
使用OpenId和OAuth进行第三方登录 -
tornado.database
MySQL服务端封装 -
tornado.platform.twisted
在Tornado上运行Twisted实现的代码 -
tornado.websocket
实现和浏览器的双向通信 -
tornado.wsgi
其他Python网络框架或服务器的相互操作
- Utilities 应用模块
-
tornado.autoload
产生环境中自动检查代码更新 -
tornado.gen
基于生成器的接口,使用该模块 保证代码异步运行。 -
tornado.httputil
分析HTTP请求内容 -
tornado.options
解析终端参数 -
tornado.process
多进程实现的封装 -
tornado.stack_context
异步环境中对回调函数上下文保存、异常处理 -
tornado.testing
单元测试
Tornado服务器的三个底层核心模块
-
httpserver
服务于web模块的一个简单的HTTP服务器的实现
Tornado的HTTPConnection
类用来处理HTTP请求,包括读取HTTP请求头、读取POST传递的数据,调用用户自定义的处理方法,以及把响应数据写给客户端的socket
。 -
iostream
对非阻塞式的socket
的封装以便于常见读写操作
为了在处理请求时实现对socket
的异步读写,Tornado实现了IOStream
类用来处理socket
的异步读写。 -
ioloop
核心的I/O循环
Tornado为了实现高并发和高性能,使用了一个IOLoop
事件循环来处理socket
的读写事件,IOLoop
事件循环是基于Linux的epoll
模型,可以高效地响应网络事件,这是Tornado高效的基础保证。
设计模型
Tornado不仅仅是一个Web框架,它完整地实现了HTTP服务器和客户端,再此基础上提供了Web服务,它可分为四层:
- Web框架:最上层,包括处理器、模板、数据库连接、认证、本地化等Web框架所需功能。
- HTTP/HTTPS层:基于HTTP协议实现了HTTP服务器和客户端
- TCP层:实现TCP服务器负责数据传输
- Event层:最底层、处理IO事件
一个请求处理的处理过程
- 服务器绑定
bind
到特定端口port
,然后开始监听listen
客户端的请求。 - 当客户端连接
connect
到来时,会将请求发送给服务器。 - 服务器处理请求完毕后返回响应结果给客户端
当需要处理成千上万的连接的时候,就会遇到典型的The C10K Program
问题,常见的解决方案有
- 一个线程服务多个客户端,使用非阻塞I/O和水平触发的就绪通知。
- 一个线程服务多个客户端,使用非阻塞I/O和就绪改变时通知。
- 一个服务线程服务于多个客户端,使用异步I/O。
- 一个服务线程服务于一个客户端,使用阻塞I/O。
- 将服务代码编译进内核
Tornado采用的方式是“多进程 + 非阻塞 + epoll模式”
安装配置
参考资料
- GitHub主页 https://github.com/tornadoweb/tornado
- 官方网站 http://www.tornadoweb.org/en/stable/
- Tornado4.3 https://tornado-zh.readthedocs.io/zh/latest/
- Tornado6.0 https://www.osgeo.cn/tornado/index.html
- Python下载列表 https://www.python.org/downloads/
版本问题
- Tornado4.3 可以运行在Python2.6、2.7、3.2+
- Tornado6.0需要Python3.5.2+
CentOS安装Tornado
环境检查
# 查看python版本 $ python --version Python 2.7.5 $ python Python 2.7.5 (default, Apr 9 2019, 14:30:50) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
CentOS安装Python3.7并保留Python2
# 安装依赖 $ yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel gcc gcc-c++ # 创建目录 $ mkdir /usr/local/python3 # 下载编译安装 $ wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz $ tar -zxvf Python3.7.3.tgz && cd Python3.7.3 $ ./configure $ make && make install # 创建软连接 $ ln -s /usr/local/python3/bin/python3 /usr/bin/python3 $ ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 # 测试版本 $ python3 -V Python 3.7.3 $ pip3 -v pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7) # 使用pip3安装tornado $ pip3 install tornado Collecting tornado Using cached https://files.pythonhosted.org/packages/30/78/2d2823598496127b21423baffaa186b668f73cd91887fcef78b6eade136b/tornado-6.0.3.tar.gz Installing collected packages: tornado Running setup.py install for tornado ... done Successfully installed tornado-6.0.3 You are using pip version 19.0.3, however version 19.1.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. # 测试tornado $ python3 Python 3.7.3 (default, Jul 8 2019, 17:00:47) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import tornado
CentOS安装配置pip
# CentOS安装EPEL扩展源 $ yum install -y epel-release # CentOS安装Python Pip $ yum install -y python-pip # 查看pip版本 $ pip --version pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7) # 更新pip $ pip install --upgrade pip $ pip --version pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
可直接使用git clone
从GitHub中克隆下载Tornado源码,也可以使用pip
命令安装Tornado。
# 使用GitHub克隆源码包 $ git clone https://github.com/tornadoweb/tornado $ cd tornado $ python setup.py install
检查当前Python环境
$ python -V Python 3.7.3 $ pip -V pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
当前 Tornado 版本为 6.0.3,Tornado6.0开始将抛弃对Python2.7和3.4的支持,同时将Python3.5.2作为最低支持版本。
Ubuntu安装Tornado
Ubuntu自身安装了Python2和Python3,默认采用的是Python3,使用时需要进行切换版本。
检查当前系统环境
$ uname -a # 检查内核版本 $ uname -r
检查当前Python环境
$ python -V Python 2.7.15+ $ whereis python python: /usr/bin/python2.7 /usr/bin/python2.7-config /usr/bin/python3.6 /usr/bin/python /usr/bin/python3.6m /usr/lib/python2.7 /usr/lib/python3.6 /usr/lib/python3.7 /etc/python2.7 /etc/python3.6 /etc/python /usr/local/lib/python2.7 /usr/local/lib/python3.6 /usr/include/python2.7 /usr/include/python3.6m /usr/share/python /usr/share/man/man1/python.1.gz $ pip -V pip 9.0.1 from /usr/lib/python2.7/dist-packages (python 2.7) $ whereis pip pip: /usr/bin/pip /usr/share/man/man1/pip.1.gz $ type pip pip 已被录入哈希表 (/usr/bin/pip) $ python2 -V Python 2.7.15+ python2: /usr/bin/python2.7 /usr/bin/python2.7-config /usr/bin/python2 /usr/lib/python2.7 /etc/python2.7 /usr/local/lib/python2.7 /usr/include/python2.7 /usr/share/man/man1/python2.1.gz $ python3 -V Python 3.6.8 $ whereis python3 python3: /usr/bin/python3 /usr/bin/python3.6 /usr/bin/python3.6m /usr/lib/python3 /usr/lib/python3.6 /usr/lib/python3.7 /etc/python3 /etc/python3.6 /usr/local/lib/python3.6 /usr/include/python3.6m /usr/share/python3 /usr/share/man/man1/python3.1.gz
安装Python3
$ sudo apt-get update
$ sudo apt-get install python3
调整Python3的优先级
$ sudo update-alternative --install /usr/bin/python3 python3 /usr/bin/python3.6 1
调整Python版本的默认值为Python3
$ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 100 $ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 150 $ python -V Python 3.6.8
安装pip
$ sudo apt install python-pip $ sudo apt install python3-pip $ pip -V pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6) $ pip2 -V pip 19.1.1 from /usr/local/lib/python2.7/dist-packages/pip (python 2.7) $ pip3 -V pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6) $ whereis pip3 pip3: /usr/bin/pip3 /usr/share/man/man1/pip3.1.gz
更新pip为最新版
$ sudo pip install --upgrade pip $ pip -V Traceback (most recent call last): File "/usr/bin/pip", line 9, in <module> from pip import main ModuleNotFoundError: No module named 'pip' $ sudo pip -V # pip版本回退 $ sudo python3 -m pip install --user --upgrade pip==9.0.1
卸载pip
$ sudo apt-get remove python-pip $ sudo apt-get remove python2-pip $ duso apt-get remove python3-pip
查看pip已安装列表
$ pip list
安装Tornado
$ pip install tornado Successfully installed tornado-6.0.3 $ python >>> import tornado
开发流程
首先创建Web应用程序Application
,并将指定处理器Handler
传递过去,然后开始监听指定端口,最后启动事件循环,并开始监听网络事件,主要是socket
的读写操作。
创建脚本
$ vim server.py
#! /usr/bin/python # encoding:utf-8 # 导入Tornado模块 import tornado.ioloop #核心IO循环模块 import tornado.httpserver #异步非阻塞HTTP服务器模块 import tornado.web #Web框架模块 import tornado.options #解析终端参数模块 #从终端模块中导出define模块用于读取参数,导出options模块用于设置默认参数 from tornado.options import define, options # 定义端口用于指定HTTP服务监听的端口 # 如果命令行中带有port同名参数则会称为全局tornado.options的属性,若没有则使用define定义。 define("port", type=int, default=8000, help="run on the given port") # 创建请求处理器 # 当处理请求时会进行实例化并调用HTTP请求对应的方法 class IndexHandler(tornado.web.RequestHandler): # 定义get方法对HTTP的GET请求做出响应 def get(self): # 从querystring查询字符串中获取id参数的值,若无则默认为0. id = self.get_argument("id", 0) # write方法将字符串写入HTTP响应 self.write("hello world id = " + id) # 创建路由表 urls = [(r"/", IndexHandler),] # 定义服务器 def main(): # 解析命令行参数 tornado.options.parse_command_line() # 创建应用实例 app = tornado.web.Application(urls) # 监听端口 app.listen(options.port) # 创建IOLoop实例并启动 tornado.ioloop.IOLoop.current().start() # 应用运行入口,解析命令行参数 if __name__ == "__main__": # 启动服务器 main()
运行服务器
$ python server.py
运行测试
$ curl http://127.0.0.1:8000
进程监控
SuperVisor是一个进程监控程序,当进程需要不间断运行时由于各种原因可能中断,当进程中断时希望能够自动重启,此时就可以使用SuperVisor。
# 安装 $ pip install supervisor # 配置 $ echo_supervisord_conf > /etc/supervisord.conf # 开启配置 $ vim /etc/supervisord.conf ;[include] files = /etc/supervisor/*.ini # 创建配置 $ mkdir /etc/supervisor $ cd /etc/supervisor $ vim tornado.ini [program:tornado] command=python3 /home/tornado/server.py --port=8000 directory=/home/tornado/ autorestart=true redirect_stderr=true # 重启进程 $ ps aux | grep supervisord $ supervisord -c /etc/supervisor.conf
调试模式
$ vim server.py
应用程序执行后会首先解析并选择参数,然后创建有一个Application
实例并传递给HTTPServer实例并启动。到此HTTPServer启动,tornado.httpserver
模块用来支持非阻塞的HTTPServer。启动服务器后还需启动IOLoop实例以启动事件循环机制,配合非阻塞HTTPServer一起工作。
代码组织结构
注释 import 语句 定义选项参数 Application定义 BaseHandler定义 xxxHandler定义 main()定义
#! /usr/bin/python # encoding:utf-8 from tornado.ioloop import IOLoop from tornado.httpserver import HTTPServer from tornado.web import Application, RequestHandler, url #从终端模块中导出define模块用于读取参数,导出options模块用于设置默认参数 from tornado.options import define, options #开始调试模式 import tornado.autoreload # 定义端口用于指定HTTP服务监听的端口 # 如果命令行中带有port同名参数则会称为全局tornado.options的属性,若没有则使用define定义。 define("port", type=int, default=8000, help="run on the given port") # 调试模式 define("debug", type=bool, default=True, help="debug mode") # 创建请求处理器 # 当处理请求时会进行实例化并调用HTTP请求对应的方法 class MainHandler(RequestHandler): # 定义get方法对HTTP的GET请求做出响应 def get(self, *args, **kwargs): # 从querystring查询字符串中获取id参数的值,若无则默认为0. id = self.get_query_argument("id", strip=True) # write方法将字符串写入HTTP响应 self.write("hello world id = " + str(id)) # 创建路由表 urls = [ (r"/", MainHandler), (r"/index", MainHandler) ] # 创建配置 settings = dict( debug = options.debug ) # 创建应用 def make_app(): return Application(urls, settings) # 定义服务器 def main(): # 解析命令行参数 options.parse_command_line() # 创建应用 app = make_app() # 创建HTTP服务器实例 server = HTTPServer(app) # 监听端口 server.listen(options.port) # 创建IOLoop实例并启动 IOLoop.current().start() # 应用运行入口,解析命令行参数 if __name__ == "__main__": # 启动服务器 main()
运行测试
$ python server.py
浏览器访问http://127.0.0.1:8000?id=1000
查看服务器输出
定义选项参数
Tornado提供了tornado.options.define
方法用来简化选项参数的定义。
# 定义端口 define("port", type=int, default=8000, help="run on the given port") # 定义调试模式 define("debug", type=bool, default=True, help="debug mode")
解析命令行参数
tornado.options.parse_command_line()
创建Web应用实例
app = tornado.web.Application(urls, settings)
Tornado中Application
应用类是Handler处理器的集合
Application
类的__init__
初始化函数原型
# 原型 def __init__(self, handlers=None, default_host="", transforms=None, wsgi=False, **settings):
Tornado的HTTPServer
会负责解析用户的HTTPRequest
,构造一个request
对象。并交给RequestHandler
处理,Request
的解析是一个规划化的流程,针对Request
的处理函数RequestHandler
是被自定义的重点部分。
由于HTTP是工作在TCP协议之上的,HTTPServer
其实是TCPServer
的派生类,常规socket
编程中启动一个TCPServer有三个必备步骤:
- 创建
socket
- 绑定指定地址的端口
- 执行监听
TCPServer类的实现借鉴UNIX/Linux中的Socket机制,也必然存在上述步骤,这几个步骤都是在HTTPServer.listen()
函数调用时完成的。
server.listen(options.port)
listen
函数的参数是端口号,端口定义可通过define
来定义。from tornado.options import define, options define("port", default=8888, help="run on the given port", type=int)
define
函数是OptionParser
类的成员,定义在tornado/options.py
文件中,机制于parse_command_line()
类似。define
定义端口port
或,port
变量会被存放在options
对象的directory
成员中,因此可直接使用options.port
访问。
当使用server.listen(options.port)
后,服务器就会在端口上启动一个服务,并开始监听客户端的连接。对于常规的Socket操作,listen
之后的操作应该是accept
。
在Tornado中accept
操作是这样的:
tornado.ioloop.IOLoop.current().start()
IOLoop是什么呢?IOLoop于TCPServer之间的关系其实很简单。例如使用C语言编写TCP服务器时,编写完create-bind-listen
三段式之后,都需要编写accept/recv/send
处理客户端请求。通常会写一个无限循环,不断调用accept
来响应客户端连接,其实这个无线循环就是Tornado中的IOLoop。
IOLoop会负责accept
这一步,对于recv/send
操作通常也是在一个循环中进行的,也可以抽象成IOLoop。
最后,简单梳理下整个流程:当我们使用在客户端浏览器地址栏中输入http://127.0.0.1:8000?id=1000
时,浏览器首先会连接服务器 ,将HTTP请求发送到HTTPServer中,HTTPServer会先解析请求parse request
,然后将请求request
交给第一个匹配到的处理器Handler
。处理器Handler
会负责组织数据并调用发送API将数据发送到客户端。
核心组件
Tornado的Web服务器通常包含四大组件
ioloop
实例
tornado.ioloop
是全局Tornado的IO事件循环,是服务器的引擎核心。
tornado.ioloop
是核心IO循环模块,封装了Linux的epoll
和BSD的kqueue
,是Tornado高性能处理的核心。
tornado.ioloop.IOLoop.current()
返回当前线程的IOLoop
实例对象
tornado.ioloop.IOLoop.current().start()
用于启动IOLoop
实例对象的IO循环并开启监听
# 加载Tornado核心IO事件循环模块 import tornado.ioloop # 默认Tornado的ioloop实例 tornado.ioloop.IOLoop.current()
app
实例
app
实例代表了一个完成的后端应用,它会挂接一个服务端套接字端口并对外提供服务,一个ioloop
事件循环实例中可以包含多个app
实例。
# 创建应用实例 app = tornado.web.Application(urls) # 监听端口 app.listen(options.port)
urls
路由表
路由表用于将指定URL规则和处理器Handler挂接起来形成路由映射表,当请求到来时会根据请求的访问URL查询路由映射表来查询对应业务的处理器Handler
。
urls = [(r"/", MainHandler),]
handler
类
handler
类代表着业务逻辑,在进行服务端开发时也就是在编写处理器,用以服务客户端请求。
# 当处理请求时会进行实例化并调用HTTP请求对应的方法 class MainHandler(tornado.web.RequestHandler): # 定义get方法对HTTP的GET请求做出响应 def get(self): # 从querystring查询字符串中获取id参数的值,若无则默认为0. id = self.get_argument("id", 0) # write方法将字符串写入HTTP响应 self.write("hello world id = " + id)
四大组件的关系
- 一个IO事件循环
ioloop
可以包含多个应用app
,即可以管理多个服务端口。 - 一个应用
app
可以包含一个路由表urls
- 一个路由表
urls
可以包含多个处理器Handler
ioloop
是服务的引擎核心是发动机,负责接收和响应客户端请求,负责驱动业务处理器handler
的运行,负责服务器内部定时任务的执行。同一个ioloop
实例会运行在一个单线程环境下。
ioloop
会读取请求并解包形成 一个HTTP请求对象,并找到该套接字上对应应用app
的路由表urls
,通过请求对象的URL查询路由表中挂接的处理器Handler
,然后执行处理器Handler
。handler
处理器执行后会返回一个对象,ioloop
负责将对象包装成HTTP响应对象并序列化发送给客户端。