Tornado龙卷风是一个开源的网络服务器框架,它是基于社交聚合网站FriendFeed的实时信息服务开发而来的。2007年由4名Google前软件工程师一起创办了FriendFeed,旨在使用户能够方便地跟踪好友在Facebook和Twitter等多个社交网站上的活动。结果两年后,Facebook宣布收购FriendFeed,交易价格约为5000万美元。而此时FriendFeed只有12名员工。据说这帮人后来又到了Google,搞出了现在的Google App Engine...
我们开发这个Web服务器的主要目的就是为了处理FriendFeed的实时功能 -- 在FriendFeed的应用里每个活动用户都会保持着一个服务器连接。
Tornado使FriendFeed使用的可扩展的非阻塞Web服务器及其相关工具的开源版本,这个Web框架看起来有些像web.py或 Google的webapp,不过为了更加有效地利用非阻塞服务器环境,Tornado这个Web框架还包含了一些相关的有用工具和优化。
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和需要与每个用户建立持久连接的应用。
Tornado是基于Bret Taylor和其他人员为FrientFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不同于那些最多只能达到1w并发连接的传统网络服务器。Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使其成为一个拥有高性能的框架。
RequestHandler
用于创建Web应用程序和各种支持类的子类HTTPServer
和AsyncHTTPClient
IOLoop
和IOStream
作为HTTP组件的构建块Tornado的Web框架和HTTP服务器一起提供了完整的堆栈替代方案WSGI
Tornado是一个轻量级框架,它的模块不多最重要的模块是web,web模块包含了Tornado大部分主要功能的Web框架,其他模块都是工具性质的,以便让Web模块更加有用。
tornado.web
包括Web框架大部分主要功能,包括RequestHandler
和Application
类。tornado.httpserver
一个无阻塞HTTP服务器的实现tornado.template
模板系统tornado.escape
HTML、JSON、URLs等编码解码和字符串操作tornado.locale
国际化支持tornado.ioloop
核心IO循环tornado.iostream
对非阻塞的Socket的简单封装以方便常用读写操作tornado.httpclient
无阻塞的HTTP服务器实现tornado.netutil
网络应用的实现主要是TCPServer类tornado.auth
使用OpenId和OAuth进行第三方登录tornado.database
MySQL服务端封装tornado.platform.twisted
在Tornado上运行Twisted实现的代码tornado.websocket
实现和浏览器的双向通信tornado.wsgi
其他Python网络框架或服务器的相互操作tornado.autoload
产生环境中自动检查代码更新tornado.gen
基于生成器的接口,使用该模块 保证代码异步运行。tornado.httputil
分析HTTP请求内容tornado.options
解析终端参数tornado.process
多进程实现的封装tornado.stack_context
异步环境中对回调函数上下文保存、异常处理tornado.testing
单元测试Tornado服务器的三个底层核心模块
httpserver
服务于web模块的一个简单的HTTP服务器的实现HTTPConnection
类用来处理HTTP请求,包括读取HTTP请求头、读取POST传递的数据,调用用户自定义的处理方法,以及把响应数据写给客户端的socket
。iostream
对非阻塞式的socket
的封装以便于常见读写操作socket
的异步读写,Tornado实现了IOStream
类用来处理socket
的异步读写。ioloop
核心的I/O循环IOLoop
事件循环来处理socket
的读写事件,IOLoop
事件循环是基于Linux的epoll
模型,可以高效地响应网络事件,这是Tornado高效的基础保证。Tornado不仅仅是一个Web框架,它完整地实现了HTTP服务器和客户端,再此基础上提供了Web服务,它可分为四层:
使用Tornado可以方便地架构出各种类型的web服务器,以HTTP服务器为例来分析下web服务器的工作方式。
一个请求处理的处理过程
bind
到特定端口port
,然后开始监听listen
客户端的请求。connect
到来时,会将请求发送给服务器。当需要处理成千上万的连接的时候,就会遇到典型的The C10K Program
问题,常见的解决方案有
Tornado采用的方式是“多进程 + 非阻塞 + epoll模式”
参考资料
版本问题
环境检查
# 查看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自身安装了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
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)
四大组件的关系
ioloop
可以包含多个应用app
,即可以管理多个服务端口。app
可以包含一个路由表urls
urls
可以包含多个处理器Handler
ioloop
是服务的引擎核心是发动机,负责接收和响应客户端请求,负责驱动业务处理器handler
的运行,负责服务器内部定时任务的执行。同一个ioloop
实例会运行在一个单线程环境下。
当一个请求到来时,IO事件循环ioloop
会读取请求并解包形成 一个HTTP请求对象,并找到该套接字上对应应用app
的路由表urls
,通过请求对象的URL查询路由表中挂接的处理器Handler
,然后执行处理器Handler
。handler
处理器执行后会返回一个对象,ioloop
负责将对象包装成HTTP响应对象并序列化发送给客户端。
未完待续...