tornado的基本组成与实践
-
web服务器组成。 一个tornado web服务器通常由四大组件组成。
ioloop
实例,它是全局的tornado事件循环,是服务器的引擎核心。app
实例,它代表一个完成的后端app,它会挂接一个服务端套接字端口对外提供服务。可以有多个,但是一般只使用一个。handler
类,它代表业务逻辑,我们进行服务端开发时就是编写多个handler
用来服务客户端请求。- 路由表,它将指定的url规则和handler挂接起来,形成一个路由映射表。当请求到来时,根据请求的访问url查询路由映射表来找到相应的业务handler。
-
组件之间的关系
- 一个ioloop包含多个app(管理多个服务端口),一个app包含一个路由表,一个路由表包含多个handler。ioloop是服务的引擎核心,它是发动机,负责接收和响应客户端请求,负责驱动业务handler的运行,负责服务器内部定时任务的执行。
-
示例
当一个请求到来时,ioloop读取这个请求解包成一个http请求对象,找到该套接字上对应app的路由表,通过请求对象的url查询路由表中挂接的handler,然后执行handler。handler方法执行后一般会返回一个对象,ioloop负责将对象包装成http响应对象序列化发送给客户端。

同一个ioloop实例运行在一个单线程环境下。
实例
下面实现一个只有两个API的服务。
import tornado.ioloop
import tornado.web
import asyncio
class FactorialService(object): # 定义一个阶乘服务对象
def __init__(self):
self.cache = {} # 用字典记录已经计算过的阶乘
def calc(self, n):
if n in self.cache: # 如果有直接返回
return self.cache[n]
s = 1
for i in range(1, n):
s *= i
self.cache[n] = s # 缓存起来
return s
class FactorialHandler(tornado.web.RequestHandler):
service = FactorialService() # new出阶乘服务对象
async def get(self):
n = int(self.get_argument("n")) # 获取url的参数值
print("get n = {}".format(n))
await asyncio.sleep(5)
self.write(str(self.service.calc(n))) # 使用阶乘服务
print("FactorialHandler end")
class SayHelloHanlder(tornado.web.RequestHandler):
async def get(self):
name = self.get_argument("name") # 获取url的参数值
print("SayHelloHanlder to {}".format(name))
await asyncio.sleep(1)
self.write("hello {}".format(name))
print("SayHelloHanlder end")
def make_app():
return tornado.web.Application([
(r"/fact", FactorialHandler), # 注册路由
(r"/say/hello", SayHelloHanlder),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
- 请求两个不同的接口
分别起两个终端,分别输入下面两行。
curl http://localhost:8888/say/hello?name=lil
curl http://localhost:8888/fact?n=20
之后可以看到运行代码的终端打印出下面的运行结果:
get n = 20
SayHelloHanlder to lily
SayHelloHanlder end
FactorialHandler end
从结果可以看出,/fact
先被请求,但是结束得反而晚,而say/hello
虽然开始得晚,却比/fact
结束得早。这正说明了在单个线程中,这两个函数并发执行了。
- 请求同一个接口。
首先添加一个RandomRSleepHandler
,然后在路由表中注册一个新的路由项。
class RandomRSleepHandler(tornado.web.RequestHandler):
async def get(self):
name = self.get_argument("name")
seconds = random.randint(60, 120) # 随机产生睡眠的时间
print("RandomRSleepHandler %s sleep %s seconds" % (name, seconds))
await asyncio.sleep(seconds)
self.write("I am RandomRSleepHandler")
print("RandomRSleepHandler awake")
添加路由项。
def make_app():
return tornado.web.Application([
....
(r"/random/sleep", RandomRSleepHandler),
])
分别起两个终端,分别输入下面两行。
http://127.0.0.1:8888/random/sleep?name=lily
http://127.0.0.1:8888/random/sleep?name=huhu
服务器输出如下。
RandomRSleepHandler lily sleep 76 seconds
RandomRSleepHandler huhu sleep 73 seconds
RandomRSleepHandler huhu awake
RandomRSleepHandler lily awake
从结果可以看出,用户huhu
先发出请求,但是他获得响应的时间反而比后发出请求的用户lily
晚。服务器在收到huhu
的请求之后,通过await asyncio.sleep(seconds)
请求阻塞住了,但是,服务器进程并没有就此阻塞,而是去处理了另一个用户的请求。这正说明了在单个线程中,两个用户请求并发执行了。
而其中的await asyncio.sleep(seconds)
可以看作是具体业务逻辑的模拟。
参考文献
- 知乎后端主力框架tornado入门体验:juejin.cn/post/684490…
- 官方文档:www.tornadoweb.org/en/stable/i…
- 深入理解tornado之底层ioloop实现:segmentfault.com/a/119000000…