前言
前面几篇文章我们依次介绍了 flask、django、fastapi 这三大 python web 框架,今天来聊聊一位"资历更老"但依然活跃在长连接、高并发场景中的选手——tornado。
tornado 由 friendfeed 公司开发,2009 年被 facebook 收购后开源,最早就是为了解决 friendfeed 网站"实时信息流"场景下大量长连接、高并发的性能问题而生的。它的核心关键词是**“异步非阻塞”**——早在 python 3 原生 asyncio 出现之前(python 3.4+),tornado 就已经用自己的一套事件循环机制实现了异步 i/o,是 python 异步 web 开发的"老前辈"。
时至今日,虽然 fastapi 等基于原生 asyncio 的新框架风头正劲,但 tornado 凭借其成熟稳定的异步网络库、内置 websocket 支持,依然广泛应用于长轮询、实时推送、聊天室、监控系统等需要维持大量并发连接的场景。
本文将带你了解 tornado 的核心概念,并通过两个例子——一个基础 web 接口,一个 websocket 实时通信——体验一下 tornado 在处理长连接场景时的独特优势。
一、tornado 是什么?
tornado 全称 “tornado web server”,它既是一个 web 框架,也是一个异步网络库,可以说是"框架 + 服务器"二合一。
tornado 的主要特点
- 异步非阻塞架构:基于单线程事件循环(event loop),一个进程即可同时处理数千个长连接,不会因为某个慢请求阻塞其他请求
- 原生支持 websocket:内置
tornado.websocket模块,实现双向实时通信非常简单,天然适合聊天室、实时通知、在线协作等场景 - 自带高性能 http 服务器:不像 flask/django 需要额外的 wsgi/asgi 服务器(如 gunicorn、uvicorn),tornado 自带的服务器就能直接用于生产环境
- 与 asyncio 兼容:现代版本的 tornado 已经可以和 python 标准库的
asyncio无缝协作,可以在同一事件循环里混用两者的异步代码 - 轻量灵活:路由、模板等设计思路和 flask 比较接近,学习成本不算高,但要真正发挥其威力,需要理解异步编程的思维方式
和前面几个框架的定位差异
| 维度 | flask | django | fastapi | tornado |
|---|---|---|---|---|
| 核心定位 | 微框架 | 全功能框架 | 现代高性能 api 框架 | 异步网络库 + web 框架 |
| 并发模型 | 同步(默认) | 同步为主 | 原生异步(asgi) | 原生异步(自有事件循环,兼容 asyncio) |
| websocket | 需借助扩展 | 需借助 channels 等扩展 | 支持,但非核心卖点 | 原生内置,核心优势之一 |
| 是否自带生产级服务器 | 否 | 否 | 否 | 是 |
| 典型场景 | 小型项目、原型 | 中大型完整系统 | api 服务、微服务 | 长连接、实时推送、聊天室 |
可以看到,tornado 并不是要跟 fastapi 拼"谁的 api 开发体验更好",而是在"需要同时维持海量并发连接"这个细分场景里有着天然优势。
二、环境准备
按惯例,先创建虚拟环境:
# 创建虚拟环境 python -m venv venv # 激活虚拟环境 # windows: venv\scripts\activate # macos / linux: source venv/bin/activate
安装 tornado:
pip install tornado
查看版本确认安装成功:
python -c "import tornado; print(tornado.version)"
三、动手实践一:最基础的 web 服务
新建 app.py:
import tornado.ioloop
import tornado.web
class hellohandler(tornado.web.requesthandler):
"""处理 get / 请求的处理器"""
def get(self):
self.write({"message": "hello, tornado!"})
class greethandler(tornado.web.requesthandler):
"""演示路径参数与查询参数的处理器"""
def get(self, name):
# 通过 self.get_query_argument 获取 url 查询参数,可设置默认值
lang = self.get_query_argument("lang", default="zh")
greeting = "你好" if lang == "zh" else "hello"
self.write({"greeting": f"{greeting}, {name}!"})
def make_app():
return tornado.web.application([
(r"/", hellohandler),
(r"/greet/([^/]+)", greethandler), # 用正则捕获路径参数
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("tornado 服务已启动: http://127.0.0.1:8888")
tornado.ioloop.ioloop.current().start()运行:
python app.py
打开浏览器依次访问:
http://127.0.0.1:8888/:返回{"message": "hello, tornado!"}http://127.0.0.1:8888/greet/小明?lang=en:返回{"greeting": "hello, 小明!"}
代码要点解析
- tornado 不像 flask 用装饰器声明路由,而是用
requesthandler子类 来组织每个接口的逻辑,get()、post()等方法名对应 http 方法,这种写法更接近传统的面向对象风格。 - 路由通过正则表达式定义(如
r"/greet/([^/]+)"),括号里捕获的内容会作为参数传给对应的方法。 self.write()可以直接写入字符串或字典,字典会被自动序列化为 json。tornado.ioloop.ioloop.current().start()启动的是 tornado 自己的事件循环,这也是它能自带高性能服务器、不依赖 gunicorn/uvicorn 的原因。
四、动手实践二:一个简单的 websocket 实时聊天室
这才是 tornado 真正大放异彩的场景。下面用不到 40 行代码实现一个"多人广播聊天室":谁发一条消息,所有在线的人都能实时收到。
chat_server.py:
import tornado.ioloop
import tornado.web
import tornado.websocket
# 保存所有已连接的客户端
clients = set()
class chatwebsocket(tornado.websocket.websockethandler):
def check_origin(self, origin):
# 简单起见,允许所有来源的跨域连接(生产环境请按需收紧)
return true
def open(self):
"""客户端建立连接时触发"""
clients.add(self)
print(f"新用户连接,当前在线人数:{len(clients)}")
def on_message(self, message):
"""收到某个客户端发来的消息时触发,广播给所有在线用户"""
for client in clients:
client.write_message(f"用户说: {message}")
def on_close(self):
"""客户端断开连接时触发"""
clients.discard(self)
print(f"用户断开,当前在线人数:{len(clients)}")
class indexhandler(tornado.web.requesthandler):
"""提供一个简单的聊天页面,方便直接在浏览器里测试"""
def get(self):
self.write("""
<!doctype html>
<html><head><meta charset="utf-8"><title>tornado 聊天室</title></head>
<body>
<input id="msg" placeholder="输入消息,回车发送">
<ul id="log"></ul>
<script>
const ws = new websocket("ws://" + location.host + "/ws");
ws.onmessage = (e) => {
const li = document.createelement("li");
li.textcontent = e.data;
document.getelementbyid("log").appendchild(li);
};
document.getelementbyid("msg").addeventlistener("keydown", (e) => {
if (e.key === "enter" && e.target.value) {
ws.send(e.target.value);
e.target.value = "";
}
});
</script>
</body></html>
""")
def make_app():
return tornado.web.application([
(r"/", indexhandler),
(r"/ws", chatwebsocket),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("聊天室已启动: http://127.0.0.1:8888")
tornado.ioloop.ioloop.current().start()运行后,用两个不同的浏览器标签页打开 http://127.0.0.1:8888,在任意一个页面的输入框里发消息回车,会发现所有打开的页面都能实时收到这条广播消息——这就是 websocket 双向实时通信的效果,也是 tornado 相比 flask/django 在这类场景下最直观的优势体现。
代码要点解析
tornado.websocket.websockethandler:专门用于处理 websocket 连接的处理器基类,重写open()、on_message()、on_close()三个生命周期方法即可实现完整逻辑。open():客户端建立连接成功后触发一次,通常用来做上线记录、加入房间等初始化操作。on_message(message):每收到一条客户端消息就触发一次,这里的实现是"收到什么就广播给所有人",实际项目中可以在这里加房间、鉴权、消息持久化等逻辑。on_close():客户端断开连接(关闭页面、网络中断等)时触发,通常用来做下线清理。check_origin():tornado 默认会校验请求来源是否同源,本地测试图方便直接返回true放行所有来源,生产环境要按实际情况收紧。- 这个例子里用一个全局的
set()保存所有在线连接,单机场景下够用;如果要支持多进程/多机部署下的广播,通常需要引入 redis 的发布订阅(pub/sub)机制来做跨进程消息同步。
五、常用生态一览
| 库/工具 | 用途 |
|---|---|
| tornado.websocket | 内置 websocket 支持 |
| tornado.gen / asyncio | 编写异步协程逻辑 |
| motor | mongodb 的异步驱动,与 tornado 搭配常见 |
| aiomysql / asyncpg | mysql / postgresql 的异步驱动 |
| redis pub/sub | 多进程/多机场景下同步 websocket 广播消息 |
| supervisor / systemd | 生产环境进程守护,配合多进程部署(tornado 自带服务器,无需额外的 wsgi/asgi 网关) |
六、总结
通过本文,我们了解了:
- tornado 是一个"异步网络库 + web 框架"二合一的选手,早在 python 原生
asyncio之前就已经用自己的事件循环实现了异步非阻塞架构; - 它用面向对象的
requesthandler子类来组织路由逻辑,正则表达式定义 url 规则,风格上比 flask/fastapi 略"重"一些; - tornado 自带生产级 http 服务器,不需要额外配置 gunicorn/uvicorn 等网关;
- 原生 websocket 支持是 tornado 最具辨识度的能力,几十行代码就能搭建一个实时广播聊天室,这在需要维持海量长连接的场景(消息推送、实时监控、在线协作)中非常有优势。
至此,flask、django、fastapi、tornado 四个 python web 框架都已经介绍完毕。简单总结一下各自的"最佳应用场景":
- flask:轻量项目、原型验证、想要完全掌控技术栈的场景
- django:需要快速搭建功能完整系统(尤其带后台管理)的中大型项目
- fastapi:对外提供 api 服务、微服务、ai 模型接口,追求开发效率和运行性能
- tornado:长连接、实时推送、聊天室等对并发连接数要求苛刻的场景
没有"最好"的框架,只有"最适合当前场景"的框架,希望这个系列能帮你在做技术选型时多一份判断依据。
参考资料
- tornado 官方文档:https://www.tornadoweb.org/
- tornado github 仓库:https://github.com/tornadoweb/tornado
到此这篇关于python tornado 框架从零搭建你的第一个异步 web 应用(最新推荐)的文章就介绍到这了,更多相关python tornado 框架入门内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论