Tornado 是 Python 生态里一个比较特别的框架:它既是 Web 框架,也是一个异步网络库,官方名称是 Tornado Web Server。它由 FriendFeed 开发,2009 年被 Facebook 收购后开源,最初就是为了解决实时信息流场景下大量长连接、高并发的性能问题。它的核心关键词是“异步非阻塞”——在 Python 3.4 原生 asyncio 出现之前,Tornado 就已经用自己的一套事件循环实现了异步 I/O。
放到今天的框架对比里看:Flask 是微框架、默认同步;Django 是全功能框架、以同步为主;FastAPI 基于 ASGI 原生异步;Tornado 则用自有事件循环实现原生异步,并与 asyncio 兼容。它们最直接的差别在于,Tornado 自带高性能 HTTP 服务器,不需要额外挂 Gunicorn、Uvicorn 就能用于生产;同时内置 tornado.websocket,WebSocket 是它的核心能力而不是扩展。因此它更适合长轮询、实时推送、聊天室、监控系统这类需要维持大量并发连接的场景。
一、环境准备
先建虚拟环境并安装:
- python -m venv venv
- # Windows
- venv\Scripts\activate
- # macOS / Linux
- source venv/bin/activate
- pip install tornado
- # 确认版本
- python -c "import tornado; print(tornado.version)"
复制代码
注意激活命令在不同系统下不一样:Windows 用 venv\Scripts\activate,macOS/Linux 用 source venv/bin/activate。
二、第一个 Tornado 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, 小明!"}
代码要点:
1. 路由不靠装饰器,而是 RequestHandler 子类。每个接口写成一个类,get()、post() 等方法名直接对应 HTTP 方法,整体是面向对象风格,和 Flask 的装饰器写法区别明显。
2. URL 规则用正则表达式定义。例如 (r"/greet/([^/]+)", GreetHandler),括号里捕获的内容会作为位置参数传给 get(self, name),正则怎么写直接决定了参数怎么拿到。
3. 查询参数用 self.get_query_argument("lang", default="zh") 获取,第二个参数可指定默认值;路径参数则来自路由正则的捕获组。
4. self.write() 既能写字符串,也能直接写字典,写字典时会自动序列化成 JSON,所以上面两个接口天然返回 JSON。
5. 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 双向通信的效果。
代码要点:
1. tornado.websocket.WebSocketHandler 是处理 WebSocket 连接的基类,重写 open()、on_message()、on_close() 三个生命周期方法就能完成完整逻辑,不需要自己处理握手协议。
2. open() 在连接建立成功后触发一次,适合做上线记录、加入房间等初始化操作;on_close() 在客户端关闭页面或网络中断时触发,适合做下线清理。
3. on_message(message) 每收到一条客户端消息就触发一次。示例里是“收到什么就广播给所有人”,实际项目可以在这里叠加房间划分、鉴权、消息持久化等逻辑。
4. check_origin() 关系到跨域校验。Tornado 默认会校验请求来源是否同源,本地测试直接返回 True 放行所有来源最省事,但生产环境必须按实际域名收紧。
5. 示例用一个全局 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 网关)
五、选型参考
轻量项目、原型验证、想完全掌控技术栈,Flask 就够;需要快速搭建功能完整、尤其带后台管理的中大型系统,Django 更合适;对外提供 API 服务、微服务、AI 模型接口,追求开发效率和运行性能,可以选 FastAPI;而对并发连接数要求苛刻的长连接、实时推送、聊天室、监控系统,Tornado 的异步网络库和原生 WebSocket 才真正发挥价值。
小结:Tornado 用 RequestHandler 子类组织路由逻辑、用正则定义 URL 规则,风格比 Flask/FastAPI 略重;它自带生产级 HTTP 服务器,不需要额外配置 Gunicorn/Uvicorn;原生 WebSocket 支持让几十行代码就能搭出一个实时广播聊天室。这些特性决定了它在需要维持海量长连接的场景里依然有稳固的位置。 |