Python Tornado 完整教程
Tornado 是一个由 Python 编写、开源的、异步的网络框架和 HTTP 服务器,它的核心特点是异步非阻塞 I/O,这使得它非常适合处理高并发、长连接的场景,例如实时 Web 服务、聊天室、API 服务器等。

为什么选择 Tornado?
在 Flask、Django 等同步框架大行其道的今天,Tornado 的优势在于:
- 高性能:基于异步非阻塞 I/O 模型,可以轻松处理数以万计的并发连接,而不会因为等待 I/O(如数据库查询、网络请求)而阻塞整个程序。
- 原生 WebSocket 支持:Tornado 对 WebSocket 协议提供了第一方的、高质量的支持,构建实时应用非常方便。
- 异步友好:框架从设计之初就围绕异步编程,使得编写高并发应用逻辑更加自然。
- 自带的 HTTP 服务器:Tornado 包含了一个强大的、生产级的 HTTP 服务器,无需依赖其他 WSGI 服务器(如 Gunicorn、uWSGI)即可运行。
环境准备
你需要安装 Tornado,最简单的方式是使用 pip:
pip install tornado
第一个 Tornado 应用:Hello World
让我们从一个最简单的例子开始,理解 Tornado 的基本工作流程。
# hello_world.py
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
"""处理 GET 请求"""
self.write("Hello, Tornado World!")
def make_app():
return tornado.web.Application([
(r"/", MainHandler), # 路由映射:URL根路径 / 映射到 MainHandler
])
if __name__ == "__main__":
app = make_app()
app.listen(8888) # 监听 8888 端口
print("Server is running on http://localhost:8888")
tornado.ioloop.IOLoop.current().start() # 启动事件循环
代码解析:

import:导入了tornado.ioloop(事件循环)和tornado.web(Web 框架核心)。MainHandler(tornado.web.RequestHandler):我们创建了一个处理类,它继承自tornado.web.RequestHandler,这个类负责处理特定 URL 的 HTTP 请求。get(self):这是RequestHandler的一个方法,专门用来处理 HTTP GET 请求,当有用户访问我们注册的 URL 时,这个方法就会被调用。self.write(...):该方法将字符串内容作为 HTTP 响应体返回给客户端。make_app():这是一个工厂函数,用于创建tornado.web.Application实例。[(r"/", MainHandler)]:这是一个路由列表,它告诉 Tornado,当访问根路径 时,将请求交给MainHandler类来处理。app.listen(8888):让应用程序监听本地的 8888 端口。tornado.ioloop.IOLoop.current().start():这是 Tornado 程序的“心脏”,它启动了事件循环,让程序能够持续接收和处理请求,没有这一行,服务器启动后会立即退出。
运行你的应用:
python hello_world.py
然后在浏览器中访问 http://localhost:8888,你就能看到 "Hello, Tornado World!"。
核心概念详解
路由
路由就是 URL 模式与处理类之间的映射,它是一个元组的列表,每个元组包含一个 URL 正则表达式和一个处理类。
tornado.web.Application([
(r"/", MainHandler),
(r"/story/([0-9]+)", StoryHandler), # 捕获 URL 中的数字作为参数
(r"/story/([^/]+)", StoryHandler), # 捕获 URL 中 / 之间的所有内容
])
在 RequestHandler 中,可以通过 self.request.path_args 或 self.request.path_kwargs 获取 URL 中捕获的参数。

RequestHandler
RequestHandler 是 Tornado 的核心,它封装了所有与 HTTP 请求和响应相关的操作。
- 常用方法:
self.get_argument(name, default=..., strip=True): 获取 GET 或 POST 请求中的参数。strip=True会自动去除参数两端的空白字符。self.get_arguments(name): 获取同名的多个参数(?foo=1&foo=2),返回一个列表。self.request: 一个包含所有请求信息的对象(如method,headers,body,remote_ip等)。self.set_status(code, reason=None): 设置 HTTP 响应状态码(如 200, 404, 500)。self.set_header(name, value): 设置响应头。self.add_header(name, value): 添加一个响应头。self.redirect(url): 重定向客户端到另一个 URL。self.render(template_name, **kwargs): 渲染一个模板文件,并返回给客户端。
模板
Tornado 使用自己的模板语言,它支持 Python 的控制流语句。
模板文件 template.html:
<html>
<head><title>{{ title }}</title></head>
<body>
<h1>{{ header }}</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
</body>
</html>
Python 代码:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("template.html",
title="My Page",
header="Hello from Tornado Template",
items=["Item 1", "Item 2", "Item 3"])
# ... make_app and main 函数 ...
使用模板的步骤:
- 创建一个
templates文件夹,将模板文件(如template.html)放进去。 - 在创建
Application时,指定template_path参数。def make_app(): return tornado.web.Application([ (r"/", MainHandler), ], template_path="templates") # 指定模板目录 - 在
RequestHandler中调用self.render()方法。
异步编程与协程
这是 Tornado 最强大的部分,当你的应用需要执行 I/O 密集型操作(如数据库查询、调用其他 API)时,不能使用同步的方式,否则会阻塞整个事件循环,导致服务器无法响应其他请求。
Tornado 使用基于 async/await 语法的协程来实现异步。
同步 vs. 异步示例
假设我们有一个非常耗时的同步函数 sync_fetch_db()。
import time
import tornado.ioloop
import tornado.web
# 模拟一个耗时的同步操作
def sync_fetch_db():
time.sleep(5) # 阻塞 5 秒
return "Data from DB"
class SyncHandler(tornado.web.RequestHandler):
def get(self):
start_time = time.time()
data = sync_fetch_db()
self.write(f"Sync Result: {data}. Time taken: {time.time() - start_time:.2f}s")
# ... make_app ...
当你访问这个处理程序时,服务器会完全卡死 5 秒,这 5 秒内,服务器无法处理任何其他请求。
异步版本
我们用 async/await 来重写它,需要一个支持异步的客户端库,aiobotocore (AWS), aiomysql (MySQL), 或者 aiohttp (HTTP 请求),这里我们用 asyncio.sleep 来模拟异步 I/O。
import asyncio
import tornado.ioloop
import tornado.web
import tornado.httpclient # Tornado 的异步 HTTP 客户端
# 模拟一个异步操作
async def async_fetch_data():
# 模拟网络延迟
await asyncio.sleep(3)
return "Data from Async API"
class AsyncHandler(tornado.web.RequestHandler):
async def get(self):
start_time = time.time()
# 关键:使用 await 来等待异步操作完成
# 这期间,事件循环可以去处理其他请求
data = await async_fetch_data()
self.write(f"Async Result: {data}. Time taken: {time.time() - start_time:.2f}s")
# ... make_app ...
async/await 的工作原理:
async def get(self)::声明get方法是一个异步函数。await some_async_function():当执行到await时,Tornado 会暂停这个get方法的执行,并将控制权交还给事件循环,事件循环会去处理其他活跃的请求或连接。- 当
some_async_function完成后,事件循环会恢复get方法的执行,从await的下一行代码继续。
最佳实践:永远不要在异步处理函数中进行同步 I/O 操作,如果你必须调用一个同步的库(比如某些数据库驱动),请在一个线程池中运行它,以避免阻塞事件循环。
import concurrent.futures
# ... 在 RequestHandler 中 ...
def get(self):
# 在线程池中运行同步函数
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(sync_fetch_db)
data = future.result() # 阻塞等待线程池中的任务完成,但不会阻塞事件循环
self.write(data)
实战案例:构建一个简单的实时聊天室
这个例子将结合 Tornado 的路由、模板、异步和 WebSocket 功能。
项目结构
chat_app/
├── templates/
│ └── index.html
├── chat.py
代码 (chat.py)
import tornado.ioloop
import tornado.web
import tornado.websocket
import uuid
import json
# 用于存储所有连接的客户端
clients = set()
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class ChatWebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
"""当新的 WebSocket 连接建立时调用"""
print("New client connected")
# 为每个连接生成一个唯一的 ID
self.client_id = str(uuid.uuid4())
clients.add(self)
# 通知所有客户端有新用户加入
self.broadcast_message({
"type": "system",
"message": f"User {self.client_id[:8]} joined."
})
def on_message(self, message):
"""当收到客户端消息时调用"""
print(f"Received message: {message}")
try:
data = json.loads(message)
# 广播消息给所有客户端
self.broadcast_message({
"type": "user",
"user_id": self.client_id[:8],
"message": data['message']
})
except json.JSONDecodeError:
pass
def on_close(self):
"""当 WebSocket 连接关闭时调用"""
print("Client disconnected")
clients.discard(self)
# 通知所有用户有用户离开
self.broadcast_message({
"type": "system",
"message": f"User {self.client_id[:8]} left."
})
def broadcast_message(self, message):
"""向所有连接的客户端广播消息"""
for client in clients:
try:
client.write_message(json.dumps(message))
except:
# 如果客户端已经断开,则从集合中移除
clients.discard(client)
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/ws", ChatWebSocketHandler),
], template_path="templates")
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Chat server is running on ws://localhost:8888")
tornado.ioloop.IOLoop.current().start()
模板 (templates/index.html)
<!DOCTYPE html>
<html>
<head>Tornado Chat</title>
<style>
body { font-family: sans-serif; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
#message-input { width: 80%; padding: 5px; }
#send-button { padding: 5px 10px; }
.system { color: #888; font-style: italic; }
</style>
</head>
<body>
<h1>Tornado Chat Room</h1>
<div id="messages"></div>
<input type="text" id="message-input" placeholder="Type a message...">
<button id="send-button">Send</button>
<script>
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
// 连接到 WebSocket 服务器
const ws = new WebSocket("ws://localhost:8888/ws");
ws.onopen = function(event) {
console.log("Connected to chat server.");
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
const messageElement = document.createElement('div');
if (data.type === 'system') {
messageElement.className = 'system';
messageElement.textContent = data.message;
} else {
messageElement.textContent = `<${data.user_id}>: ${data.message}`;
}
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight; // 自动滚动到底部
};
ws.onclose = function(event) {
console.log("Disconnected from chat server.");
};
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
ws.send(JSON.stringify({ message: message }));
messageInput.value = '';
}
}
sendButton.onclick = sendMessage;
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
运行聊天室:
- 确保文件结构正确。
- 在
chat_app目录下运行python chat.py。 - 在浏览器中打开两个或多个标签页,访问
http://localhost:8888。 - 你就可以看到实时聊天效果了!
进阶主题
- 认证与授权:Tornado 提供了
tornado.web.authenticated装饰器,在处理类上使用它,如果用户未登录,Tornado 会自动重定向到配置的login_url,你需要自己实现get_current_user()方法,通常从 cookie 或 session 中获取用户信息。 - 静态文件:在
Application中配置static_path参数,Tornado 就可以提供 CSS、JS、图片等静态文件。tornado.web.Application([ # ... ], static_path="static") - 部署:Tornado 自带的服务器虽然强大,但在生产环境中,通常会使用 Nginx 或 Apache 作为反向代理,Nginx 负责处理静态文件、HTTPS 终止和负载均衡,并将动态请求转发给 Tornado 应用,这可以大大提高性能和安全性。
- 测试:可以使用
tornado.testing模块中的AsyncHTTPTestCase来编写异步的单元测试,方便地测试你的 Web 应用。
Tornado 是一个功能强大且性能卓越的 Python Web 框架,尤其适合构建需要处理大量并发连接的实时应用。
- 入门:从
RequestHandler、路由和模板开始,理解其同步式的 API。 - 进阶:深入理解异步编程,熟练使用
async/await,避免在异步代码中执行同步 I/O。 - 实战:通过构建 WebSocket 应用(如聊天室)来巩固对异步和长连接的理解。
- 生产:学习使用 Nginx 进行反向代理部署,并关注认证、日志等生产环境要素。
希望这份教程能帮助你快速上手 Tornado!
