前言
前面几篇文章我们依次介绍了 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 开发体验更好",而是在"需要同时维持海量并发连接"这个细分场景里有着天然优势。
二、环境准备
按惯例,先创建虚拟环境:
bash
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# macOS / Linux:
source venv/bin/activate
安装 Tornado:
bash
pip install tornado
查看版本确认安装成功:
bash
python -c "import tornado; print(tornado.version)"
三、动手实践一:最基础的 Web 服务
新建 app.py:
python
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()
运行:
bash
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:
python
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