Tornado是一个可扩展的非阻塞式Web服务器及其相关工具的开源版本。Tornado每秒可以处理上千的连接,对于实时Web服务器来说,Tornado是一个理想的框架。
Tornado介绍
Tornado使用Python编写的一个强大的可扩展Web服务器,在处理高网络流量时表现的足够强健。相比于其它Python网络框架,Tornado的特点很多:
完备的Web框架:与Django、Falsk一样,Tornado也提供URL路由映射、Request上下文、基于模板的页面渲染技术。
高效的网络库:提供异步I/O支持、超时事件处理。
高效的HTTPClient:不仅仅是服务器端框架,Tornado还提供基于异步框架的HTTP客户端。
高效的内部HTTP服务器:Tornado的HTTP服务器与Tornado异步调用紧密结合,可直接用于生产环境。
完备的WebSocket支持:WebSocket是HTML5的一种新标准,实现了浏览器与服务器之间的双向实时通信。
Tornado安装
Tronado已经被配置到PyPI网站中,使用pip命令:pip install tornado即可,如果下载慢,使用国内源即可!基于Python3、Tornado6.0.4
在当今的计算机应用开发中,要做的是减少程序在I/O相关操作中的等待,提高并发程度。同步I/O操作导致请求进程阻塞,直到I/O操作完成;异步I/O操作不导致请求进程阻塞。在Python中,同步I/O可以简单理解为,一个被调用的I/O函数会阻塞调用函数的执行,而异步I/O不会阻塞调用函数的执行。
同步I/O
# Tornado的HTTP客户端
from tornado.httpclient import HTTPClient
def synchronization_wait():
"""
同步I/O操作访问www.e1yu.com,执行速度取决于网络速度和对方服务器响应速度,
只有对www.e1yu.com访问完成并获取到结果,函数才执行完毕
:return: response.body
"""
http_client = HTTPClient()
# 阻塞,直到www.e1yu.com访问完成
response = http_client.fetch("https://www.e1yu.com")
return response.body
print(synchronization_wait())
异步I/O
# Tornado的HTTP客户端
from tornado.httpclient import AsyncHTTPClient
def handle_response(response):
print(response.body)
def asynchronous_wait():
http_client = AsyncHTTPClient()
# 异步访问,http_client.fetch()在调用后立刻返回无需等待实际访问的完成
# 访问完成,AsyncHTTPClient()会调用callable参数指定的函数
http_client.fetch("https://www.e1yu.com", callable(handle_response))
print(asynchronous_wait())
可迭代与迭代器
协程是Tornado中进行异步I/O开发的方法,它使用了Python的关键字yield,yield将调用者挂起和恢复执行。在学习之前,需要了解一下这些概念。
迭代器Iterator
迭代器是访问集合内元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。迭代器不能回退,只能向前迭代!
Python中所有的Sequence(序列)类型簇都是可迭代的。使用iter()方法可以将列表、集合转换为迭代器:
num = [1, 2, 3, 4, 5, 6, 7]
iter_tor = iter(num)
#
print(iter_tor)
迭代器与普通Python对象的区别:迭代器可以调用next()函数获取一个元素,不断调用next()就能逐个访问集合中所有的元素
num = [1, 2, 3, 4, 5, 6, 7]
iter_tor = iter(num)
# 1
print(iter_tor.__next__())
# 2
print(next(iter_tor))
可以一直调用next()函数,直到返会StopIteration异常表示迭代已经完成。其中__next__()和next()函数两者实现效果一样。
定义生成器
迭代器在Python中使用范围很广,一般有两种方式定义迭代器:
实现一个(可迭代对象)Iterable,使用iter()函数获取迭代器。
用yield关键字直接将一个函数转变为迭代器,用这种方式定义的迭代器被称为生成器。
任何一个可迭代对象Iterable都可以通过iter()函数生成一个迭代器。定义一个Iterable类一般可以通过为其实现__iter__()和__next()俩个成员方法实现:
class MyIterable():
def __init__(self):
self.data = [1, 2, 3, 4]
self.step = 0
# 返回一个实现了__next__()的对象
def __iter__(self):
return self
# 返回一个元素,没有元素抛出异常
def __next__(self):
if self.step >= len(self.data):
raise StopIteration
data = self.data[self.step]
print('index:{0} call of next()'.format(self.step))
self.step += 1
return data
for i in MyIterable():
print(i)
使用生成器可以简化代码,调用任何定义中包含yield关键字的函数都会不执行该函数,而会获得一个对应该函数的生成器。
def MyIterator():
# 定义一个迭代器函数
for k, v in enumerate([1, 2, 3, 4]):
print('index:{0} call of next()'.format(k))
# 用yield返回一个元素
yield v
for i in MyIterator():
print(i)
异步和协程
异步上面简单介绍过,接下来要说的是协程。Tornado协程可以开发出类似同步代码的异步行为。编写一个简单的协程函数:
# 引入协程库gen
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
# 使用gen.coroutine装饰器,声明一个协程函数
@gen.coroutine
def coroutine_wait():
http_client = AsyncHTTPClient()
response = yield http_client.fetch("https://www.e1yu.com")
print(response.body)
Tornado协程基于Python的yield关键字,所以你不能直接像调用函数呢样调用协程。协程函数可以通过以下三种方式调用:
在本身是协程的函数内通过yield关键字调用
在IOLoop没有启动时,通过IOLoop的run_sync()函数调用
在IOLoop已经启动时,通过IOLoop的spawn_callback()函数调用
1.通过协程函数调用协程函数
# 使用gen.coroutine装饰器,声明一个协程函数
@gen.coroutine
def outer_coroutine():
print('start to call a coroutine')
yield coroutine_wait()
print('end of outer_coroutine')
outer_coroutine()
2.在IOLoop没有启动时,通过IOLoop的run_sync()函数调用
IOLoop是Tornado的主事件循环对象,Tornado程序通过监听外部客户端的访问请求,并执行相应的操作。当程序没有进入IOLoop的running状态时,可以通过run_sync()函数调用协程函数:
from tornado.ioloop import IOLoop
def func_sync():
print('start to call a coroutine')
IOLoop.current().run_sync(lambda: coroutine_wait())
print('end of outer_coroutine')
func_sync()
在普通函数中使用run_sync()函数调用,经过lambda封装的协程函数。run_sync()函数将阻塞当前函数的执行,直到被调用的协程执行完成。
Tornado要求协程函数在IOLoop的running状态中才能被调用,只不过run_sync()函数自动完成了启动、停止IOLoop的步骤,它的实现逻辑为:启动IOLoop——调用lambda封装的协程函数——停止IOLoop。
3.在IOLoop已经启动时,通过IOLoop的spawn_callback()函数调用
当Tornado程序处于running状态时的协程函数调用:
from tornado.ioloop import IOLoop
def func_sync():
print('start to call a coroutine')
IOLoop.current().spawn_callback(lambda: coroutine_wait)
print('end of outer_coroutine')
func_sync()
spawn_callback()函数将不会等待被调用协程执行完成,所以该普通函数运行只会打印两个print的结果,coroutine_wait()本身会由IOLoop在合适的时机调用。
IOLoop的spawn_callback()函数没有提供获取协程函数调用的返回值的方法,所以只能使用spawn_callback()调用没有返回值的协程函数。
协程中调用阻塞函数
协程中调用阻塞函数,会影响协程本身的性能。Tornado提供了在协程中利用线程池调度阻塞函数,不影响协程本身继续执行。
from tornado import gen
from concurrent.futures import ThreadPoolExecutor
# 实例化两个线程的线程池
thread_pool = ThreadPoolExecutor(2)
def my_sleep(count):
import time
for i in range(count):
time.sleep(1)
@gen.coroutine
def call_block():
print('start to call_block')
# 调用阻塞函数
yield thread_pool.submit(my_sleep, 10)
print('end of call_block')
# 调用没有返回值的协程函数
call_block()
在协程中等待多个异步调用
Tornado允许在协程中使用一个yield关键字等待多个异步调用,只需要把这些调用列表list或字典dictionary的方式传递给yield即可
from tornado.httpclient import AsyncHTTPClient
from tornado import gen
# 列表方式
@gen.coroutine
def coroutine_list():
http_client = AsyncHTTPClient()
# 只有在列表中的所有调用都执行完成,yield才会返回并继续执行
list_response = yield [http_client.fetch('https://www.e1yu.com'),
http_client.fetch('https://www.baidu.com'),
http_client.fetch('https://www.qq.com')
]
for response in list_response:
print(response)
# 字典方式
@gen.coroutine
def coroutine_dict():
http_client = AsyncHTTPClient()
# 只有在列表中的所有调用都执行完成,yield才会返回并继续执行
dict_response = yield {"e1yu": http_client.fetch('https://www.e1yu.com'),
"baidu": http_client.fetch('https://www.baidu.com'),
"qq": http_client.fetch('https://www.qq.com')
}
print(dict_response['e1yu'].body)
未经允许不得转载:作者:鳄鱼君,
转载或复制请以 超链接形式 并注明出处 鳄鱼君。
原文地址:《高并发处理框架 Tornado》 发布于2020-07-12