关于 Tornado
Tornado 是 FriendFeed(后被 Facebook 收购)开源的 Python Web 框架,最大特色是异步非阻塞——一台机器能扛上万的长连接,特别适合 WebSocket、聊天室、实时推送。它解决的就是著名的 C10K 问题(一台服务器同时处理 1 万个客户端)。
Tornado 自己就是一个 Web 服务器,不需要 Apache / Nginx 也能直接跑。今天写两个最小程序,先认识它的"长相"——下节课再讲异步。
装 Tornado
# 国内用清华源更快
$ pip3 install tornado -i https://pypi.tuna.tsinghua.edu.cn/simple
# 验证装好了
$ python3 -c "import tornado; print(tornado.version)"
6.4.1
/home/django/,本章统一放 /home/tornado/,方便区分:
mkdir -p /home/tornado && cd /home/tornado
HelloWorld:6 步看清 Tornado 长什么样
RequestHandler— 处理一个请求的"类"。每来一个请求,Tornado 实例化一个,调用对应的get()/post()方法。Application— 路由表的容器:URL 模式 → Handler 类。IOLoop— 事件循环。它就是那个"扛上万连接"的引擎。
新建 /home/tornado/hello.py:
# ============ Tornado 最小程序 ============
import tornado.ioloop # 事件循环
import tornado.web # Web 框架核心
# 1. 写一个 Handler——继承 RequestHandler,定义 get 方法
class MainHandler(tornado.web.RequestHandler):
def get(self):
# self.write 给浏览器写响应内容(可以是字符串、字典)
self.write("Hello, Tornado!")
# 2. 创建 Application:参数是路由表
def make_app():
return tornado.web.Application([
# 元组 (URL 正则, Handler 类)
(r"/", MainHandler),
])
# 3. 启动入口
if __name__ == "__main__":
app = make_app()
app.listen(8888) # 监听 8888 端口
print("服务器已启动:http://0.0.0.0:8888")
tornado.ioloop.IOLoop.current().start() # ★ 启动事件循环
跑起来
$ cd /home/tornado
$ python3 hello.py
服务器已启动:http://0.0.0.0:8888
浏览器访问 http://服务器IP:8888/,看到 Hello, Tornado! 就成了。
python3 hello.py)。Django/Flask 默认开发模式会自动重启,Tornado 默认不会,要在 app.listen 之后写 autoreload.start() 才有。课堂就手动重启好了。
路由怎么写
路由就是 Application 第一个参数那个列表。元组 = (URL 正则, Handler 类),可以挂多条:
tornado.web.Application([
(r"/", MainHandler), # 首页
(r"/about", AboutHandler), # 关于页
(r"/user/(\d+)", UserHandler), # 带参数:/user/123
])
(\d+) 这种捕获组会作为参数传给 handler 的方法。比如 /user/(\d+) 命中 /user/123 时,会调用 handler.get('123')——多了一个字符串参数。
区分 GET 和 POST:登录例子
def get(self):——浏览器 GET 请求时被调用;写
def post(self):——浏览器 POST 请求时被调用。同一个 URL,根据请求方法分发到不同方法。
读取参数的几种方式
| 写法 | 用来读什么 |
|---|---|
self.get_argument('name') | 同时支持 URL 查询参数 和 POST 表单字段 |
self.get_argument('name', 默认值) | 没传时不会报错,用默认值 |
self.request.method | 判断当前是 GET 还是 POST |
新建 /home/tornado/login.py:
import tornado.ioloop
import tornado.web
class LoginHandler(tornado.web.RequestHandler):
# 浏览器访问 /login 时(默认是 GET),显示登录表单
def get(self):
# 直接写 HTML 字符串。注意:表单提交回 /login 自身,method=post
self.write("""
<h2>登录</h2>
<form method='post'>
用户名:<input name='username'><br><br>
密 码:<input name='password' type='password'><br><br>
<button>登录</button>
</form>
""")
# 表单提交时(POST),处理用户提交的数据
def post(self):
# get_argument 自动从 POST 表单取值(也支持 ?xxx 的 URL 参数)
username = self.get_argument("username")
password = self.get_argument("password")
if username == "admin" and password == "123456":
self.write(f"<h2>✅ 欢迎,{username}</h2>")
else:
# 用 redirect 跳转回登录页
self.write("<h2>❌ 用户名或密码错误</h2><a href='/login'>返回</a>")
def make_app():
return tornado.web.Application([
(r"/login", LoginHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("启动:http://0.0.0.0:8888/login")
tornado.ioloop.IOLoop.current().start()
跑 python3 login.py,浏览器开 http://IP:8888/login——填表 → 提交 → 看到欢迎或报错。
xsrf_cookies=True,POST 表单就必须加 {% module xsrf_form_html() %},否则 403。课堂演示先不开。
用模板代替字符串拼 HTML
3 条模板语法(够用了)
| 语法 | 作用 |
|---|---|
{{ name }} | 填入变量值 |
{% for x in items %} ... {% end %} | 循环 |
{% if x %} ... {% end %} | 条件判断 |
{% end %}{% endfor %}、{% endif %},Tornado 统一用 {% end %}——别混了。
把 HTML 抽出来
项目结构:
新建 /home/tornado/templates/login.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"><title>登录</title>
<style>
body { font-family: sans-serif;
max-width: 360px; margin: 80px auto; }
input { width: 100%; padding: 8px; margin: 5px 0;
box-sizing: border-box; }
button { width: 100%; padding: 10px;
background: #3B6B9A; color: #fff;
border: none; cursor: pointer; }
.err { color: #B83B2E; }
</style>
</head>
<body>
<h2>🌪️ Tornado 登录</h2>
<form method="post">
<input name="username" placeholder="用户名">
<input name="password" type="password" placeholder="密码">
<button>登录</button>
</form>
<!-- {% if error %} 在 error 非空时显示一行红字 -->
{% if error %}
<p class="err">❌ {{ error }}</p>
{% end %}
<p style="color:#888">课堂账号:admin / 123456</p>
</body>
</html>
用 self.render 渲染模板
新建 /home/tornado/app.py:
import os
import tornado.ioloop
import tornado.web
class LoginHandler(tornado.web.RequestHandler):
def get(self):
# self.render 的两个参数:模板文件名、传给模板的数据(key=value)
# 这里 error="" 表示页面初次进入时没有错误信息
self.render("login.html", error="")
def post(self):
username = self.get_argument("username")
password = self.get_argument("password")
if username == "admin" and password == "123456":
# 登录成功:另开一个简单页面
self.write(f"<h2>✅ 欢迎,{username}!</h2>")
else:
# 失败:仍渲染登录页,但这次带上错误信息
self.render("login.html", error="用户名或密码错误")
def make_app():
return tornado.web.Application(
[(r"/login", LoginHandler)],
# ★ 关键设置:告诉 Tornado 模板放哪个目录
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("启动:http://0.0.0.0:8888/login")
tornado.ioloop.IOLoop.current().start()
🎉 验收
停掉之前的进程(Ctrl+C),跑 python3 app.py
访问 http://IP:8888/login,输错密码 → 页面变红字 ❌;输对 admin/123456 → 看到欢迎页 ✅
templates/ 目录放 .html② Application 里设
template_path=...③ Handler 里
self.render('xxx.html', a=1, b=2)
📋 本节小结
三个框架对比
| 主题 | Flask | Django | Tornado |
|---|---|---|---|
| 路由写法 | @app.route('/') | path('', view) | (r"/", Handler) |
| 视图形式 | 函数 | 函数 / 类 | 类(继承 RequestHandler) |
| 取参数 | request.args.get | request.GET.get | self.get_argument |
| 渲染模板 | render_template() | render(req, ...) | self.render() |
| 循环结束符 | {% endfor %} | {% endfor %} | {% end %} |
| 异步天然支持 | ❌ | 需要 ASGI | ✅ |
记住这套"三件套"启动模板
- Handler:定义
class XxxHandler(tornado.web.RequestHandler)+get/post方法 - Application:列路由表
[(r"/path", XxxHandler), ...] - 启动:
app.listen(端口)+IOLoop.current().start()
课堂练习
两组共 12 题:先 Tornado 概念,再三框架对比。
Tornado 概念基础(6 题)
把 Tornado 的核心组件和写法记住。
Tornado 处理一个请求时,最先被调用的是?
Handler 里读取 GET 参数和 POST 表单字段,统一用?
get_argument 同时处理 URL 查询参数和 POST 表单字段——比 Flask 还简单一点。下面哪一行不能省,否则 Tornado 启动后立刻退出?
start() 是阻塞的——不写它,主程序立刻结束。Application 和 listen 只是"挂好招牌",IOLoop 才是"开门迎客"。Tornado 模板里,循环的结束标签是?
{% end %} 收尾,不区分具体是结束什么。Tornado 默认开发模式下,改了代码会自动重启服务器。
用模板时,必须在 Application 中设置 template_path,否则 self.render 找不到模板文件。
template_path=os.path.join(...)。三框架对比 + 异步入门(6 题)
理解 Tornado 跟 Flask/Django 的差异,以及异步是什么。
"C10K 问题"指的是?
下面对"阻塞"的描述哪个对?
time.sleep(5)、读大文件、查慢 SQL——这 5 秒钟里别的请求都要排队等。Tornado 的非阻塞就是要避免这种排队。如果 URL 写成 r"/user/(\d+)",访问 /user/42 时 Handler 的 get 方法签名应该是?
(...) 捕获组,方法就要相应多几个参数。这里捕到 "42" 字符串。self.write 和 self.render 的区别?
同一个写"博客文章列表",三个框架最相似的是?
Tornado 自带 Web 服务器,部署上线时不需要 Nginx/Apache。