這年頭 Python web 框架是有點氾濫了. 下面要介紹的是 facebook 的開源框架 tornado. 這東西比較簡單, 並且自帶 WebSocket 支持, 能夠用它作個簡單的聊天室.
讀者最好已具有 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()
保存爲 main.py, 而後執行
$ python main.py
並訪問
http://localhost:8000/ 便可看到頁面中的 "Hello, world!".
在分支中定義的app在構造時接受的一個列表參數
[
(
'/'
,
Index
),
]
用來配置 URL 映射, 好比這裏訪問根路徑則映射至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>
''')
修改這個類後, 而後在控制檯停止服務器 (猛擊 Ctrl-C), 並從新啓動之.
如今, 訪問
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')
併爲這個類加上 URL 映射
if __name__ == '__main__':
app = tornado.web.Application([
('/', Index),
('/soc', SocketHandler),
])
app.listen(8000)
tornado.ioloop.IOLoop.instance().start()
而後重啓服務器, 並訪問
http://localhost:8000/ 就能夠在頁面上看到服務器傳來的信息了.
使用模板
在進一步完善聊天功能以前, 先整理一下代碼. 讓大坨的 HTML 出如今 Python 源碼文件中顯然是件不合適的事情. 使用render函數能夠處理模板 HTML 文件並傳遞給客戶端.
class Index(tornado.web.RequestHandler):
def get(self):
self.render('templates/index.html')
而後把先前的 HTML 內容放入 templates 目錄下的 index.html 文件中. 再重啓服務器, 這樣就將 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" 表示聊天內容
- 用戶輸入的聊天信息爲字符串
如今按照這個協議來修改 HTML
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,
})
如今重啓, 而後訪問
http://localhost:8000/, 一個簡易的聊天室就弄好了.