读者最好已具备 Javascript 与 WebSocket 的基础知识.
安装
使用 easy_install 能很方便地爬到 tornado. 或者, 下载源代码, 解包后在源码目录执行$ python setup.py build
# python setup.py install
开张
首先还是来个 hello world.import tornado.web
import tornado.ioloop
class Index(tornado.web.RequestHandler):
def get(self):
self.write('<html><body>Hello, world!')
if __name__ == '__main__':
app = tornado.web.Application([
('/', Index),
])
app.listen(8000)
tornado.ioloop.IOLoop.instance().start()
$ python main.py
在分支中定义的
app
在构造时接受的一个列表参数[
('/', Index),
]
Index
实例去处理, 在 Index
实例中, 定义的 get
方法将会处理请求.处理 WebSocket 连接
添加请求处理类
接下来就进入 WebSocket 环节. 先修改返回的页面, 让这个页面在加载后连接服务器.class Index(tornado.web.RequestHandler):
def get(self):
self.write('''
<html>
<head>
<script>
var ws = new WebSocket('ws://localhost:8000/soc');
ws.onmessage = function(event) {
document.getElementById('message').innerHTML = event.data;
};
</script>
</head>
<body>
<p id='message'></p>
''')
现在, 访问 http://localhost:8000/ 会遇到 404 错误, 因为 WebSocket 请求的 URL "ws://localhost:8000/soc" 还没有映射任何处理器, 因此这里需要再添加一个, 用于处理 WebSocket 请求的类.
import tornado.websocket
class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
self.write_message('Welcome to WebSocket')
if __name__ == '__main__':
app = tornado.web.Application([
('/', Index),
('/soc', SocketHandler),
])
app.listen(8000)
tornado.ioloop.IOLoop.instance().start()
使用模板
在进一步完善聊天功能之前, 先整理一下代码. 让大坨的 HTML 出现在 Python 源码文件中显然是件不合适的事情. 使用render
函数可以处理模板 HTML 文件并传递给客户端.class Index(tornado.web.RequestHandler):
def get(self):
self.render('templates/index.html')
管理连接者
接下来要做的一件事情是记录客户端的连接. 在SocketHandler
类里面放置一个集合, 用来记录开启着的连接.class SocketHandler(tornado.websocket.WebSocketHandler):
clients = set()
def open(self):
self.write_message('Welcome to WebSocket')
SocketHandler.clients.add(self)
def on_close(self):
SocketHandler.clients.remove(self)
class SocketHandler(tornado.websocket.WebSocketHandler):
clients = set()
@staticmethod
def send_to_all(message):
for c in SocketHandler.clients:
c.write_message(message)
def open(self):
self.write_message('Welcome to WebSocket')
SocketHandler.send_to_all(str(id(self)) + ' has joined')
SocketHandler.clients.add(self)
def on_close(self):
SocketHandler.clients.remove(self)
SocketHandler.send_to_all(str(id(self)) + ' has left')
<html>
<head>
<script>
var ws = new WebSocket('ws://localhost:8000/soc');
ws.onmessage = function(event) {
var table = document.getElementById('message');
table.insertRow().insertCell().innerHTML = event.data;
};
</script>
</head>
<body>
<table id='message'></table>
聊天功能
更换协议
之前, WebSocket 处理程序都是直接将字符串写回给客户端, 这样的问题是, 客户端很难区分是聊天信息还是系统信息. 下面规定一个简单的通信协议.- 传递给客户端的将是一个 JSON 字典
- 字典中至少包含键 "type"
- 当有用户连接或离开聊天室时, "type" 对应的值为 "sys", 并且字典中还将包含键 "message", 值为连接或离开的信息
- 当有用户输入聊天信息时, "type" 对应的值为 "user", 且字典中还将包含键 "id" 对应聊天用户的 id, 以及键 "message" 表示聊天内容
- 用户输入的聊天信息为字符串
ws.onmessage = function(event) {
var table = document.getElementById('message');
var data = eval('(' + event.data + ')');
({
'sys': function() {
var cell = table.insertRow().insertCell();
cell.colSpan = 2;
cell.innerHTML = data['message'];
},
'user': function() {
var row = table.insertRow();
row.insertCell().innerHTML = data['message'];
row.insertCell().innerHTML = data['id'];
},
}[data['type']])();
};
SocketHandler
(这里需要用到 json 库)import json
# ...
@staticmethod
def send_to_all(message):
for c in SocketHandler.clients:
c.write_message(json.dumps(message))
def open(self):
self.write_message(json.dumps({
'type': 'sys',
'message': 'Welcome to WebSocket',
}))
SocketHandler.send_to_all({
'type': 'sys',
'message': str(id(self)) + ' has joined',
})
SocketHandler.clients.add(self)
def on_close(self):
SocketHandler.clients.remove(self)
SocketHandler.send_to_all({
'type': 'sys',
'message': str(id(self)) + ' has left',
})
聊天能力, 展开!
这样每次有连接新加入或者断开, 其它页面都可以接收到消息了. 现在要做的则是接受用户聊天信息. 那么要添加一些小玩意儿到 HTML 来具备发信能力.<script>
/* ... */
function send() {
ws.send(document.getElementById('chat').value);
document.getElementById('chat').value = '';
}
</script>
</head>
<body>
<input id='chat'><button onclick='send()'>Send</button>
<table id='message' border='1'></table>
SocketHandler
加上消息处理函数class SocketHandler(tornado.websocket.WebSocketHandler):
# ...
def on_message(self, message):
SocketHandler.send_to_all({
'type': 'user',
'id': id(self),
'message': message,
})