Python Tornado 框架入门:从零搭建你的第一个异步 Web 应用

前言

前面几篇文章我们依次介绍了 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 网关)

六、总结

通过本文,我们了解了:

  1. Tornado 是一个"异步网络库 + Web 框架"二合一的选手,早在 Python 原生 asyncio 之前就已经用自己的事件循环实现了异步非阻塞架构;
  2. 它用面向对象的 RequestHandler 子类来组织路由逻辑,正则表达式定义 URL 规则,风格上比 Flask/FastAPI 略"重"一些;
  3. Tornado 自带生产级 HTTP 服务器,不需要额外配置 Gunicorn/Uvicorn 等网关;
  4. 原生 WebSocket 支持是 Tornado 最具辨识度的能力,几十行代码就能搭建一个实时广播聊天室,这在需要维持海量长连接的场景(消息推送、实时监控、在线协作)中非常有优势。

至此,Flask、Django、FastAPI、Tornado 四个 Python Web 框架都已经介绍完毕。简单总结一下各自的"最佳应用场景":

  • Flask:轻量项目、原型验证、想要完全掌控技术栈的场景
  • Django:需要快速搭建功能完整系统(尤其带后台管理)的中大型项目
  • FastAPI:对外提供 API 服务、微服务、AI 模型接口,追求开发效率和运行性能
  • Tornado:长连接、实时推送、聊天室等对并发连接数要求苛刻的场景

没有"最好"的框架,只有"最适合当前场景"的框架,希望这个系列能帮你在做技术选型时多一份判断依据。


参考资料

相关推荐
宁风1 小时前
JavaScript:函数体系-1
前端·javascript
mmsx2 小时前
Android 手簿 ADB 无线调试全攻略:USB 转 WiFi 一键连接
android·前端
guwentian2 小时前
Rolldown vs esbuild vs Webpack:Rust 打包器大战我们该怎么选
前端·webpack·rust·esbuild
特立独行的猫A2 小时前
用纯 Rust 重写 Podcastfy:把网页、PDF 和一段文字,变成一档双人对话播客
前端
ocean21032 小时前
2025-2026年Python大厂面试高频问题
python·面试·面试经验
oooo_z2 小时前
美股历史K线数据API接口选型指南:iTick覆盖1分钟到月线全周期
服务器·前端·javascript
今日无bug2 小时前
从 0 到 1 搭建端侧 AI 项目:DeepSeek-R1 + WebGPU + React + TS + Tailwind 全栈笔记
前端·react.js·typescript
mmsx2 小时前
MapLibre 实战 08|GPS 点漂了几百米才被发现:GCJ-02 纠偏原理与"转两次"陷阱
android·前端
xn71332 小时前
实测 Codex CLI 0.154.0:response.failed 为什么会被 idle timeout waiting for SSE 覆盖
python·openai