Tornado websocket应用
应用场景
WebSocket 的特点如下
- 适合服务器主动推送的场景(好友上线,即时聊天信息,火灾警告,股票涨停等)
- 相对于Ajax和Long poll等轮询技术,它更高效,不耗费网络带宽和计算资源
- 它仍然与HTTP完成网络通信
- 不受企业防火墙拦截
通信原理
1.WebSocket 客户端连接报文
GET /webfin/websocket/ HTTP/1.1 Host: localhost Upgrade: websocket # 建立webSocket链接 Connection: Upgrade # 建立链接 Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg== # 密钥 Origin: <a href="http://localhost/"><code>http://localhost</code></a>:8080 Sec-WebSocket-Version: 13 # 版本是13
客户端发起的 WebSocket 连接报文类似传统 HTTP 报文,”Upgrade:websocket”参数值表明这是 WebSocket 类型请求,“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。
2、服务端收到报文后返回的数据格式类似:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
“Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了
服务端编程:
tornadowebsocokt入口函数,需要继承tornado.websocket.WebSocketHandler,并显现open(),on_message(),on_close()函数
还提供了开发者主动操作的websocket函数
WebSocketHandler.write_meassage(message,binary=False) 用于向本链接对应的客户端写消息
WebSocketHandler.close(code=None,reason=None)函数:主动关闭链接,并告知客户端关闭的原因,code参数必须是一个数值,reason必须是一个字符串
实例
import tornado.web import tornado.ioloop import tornado.websocket from tornado.options import define,options,parse_command_line define('port',default=8888,help='run the given port',type=int) clients = dict() # 客户端session字典 class IndexHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): self.render('index.html') class MyWebSocketHandler(tornado.websocket.WebSocketHandler): def open(self, *args): # 有新连接时被调用 self.id = self.get_argument('Id') self.stream.set_nodelay(True) clients[self.id]={"id":self.id,"object":self} # 保存session到clients字典中 def on_message(self, message): # 收到消息时被调用 print("client %s received a message : %s" % (self.id,message)) def on_close(self): # 关闭链接时被调用 if self.id in clients: del clients[self.id] print("client %s is closed" % (self.id)) def check_origin(self, origin): return True app = tornado.web.Application([(r'/', IndexHandler), (r'/websocket', MyWebSocketHandler), ]) import threading import time # 启动单独的线程运行此函数,每隔1秒向所有客户端推送当前时间 def sendTime(): import datetime while True: for key in clients.keys(): msg = str(datetime.datetime.now()) clients[key]['object'].write_message(msg) # 通过WebSocketHandler.write_meassage函数推送时间消息 print("write to client %s : %s" % (key,msg)) time.sleep(1) if __name__ == '__main__': threading.Thread(target=sendTime).start() # 启动推送时间线程 parse_command_line() app.listen(options.port) tornado.ioloop.IOLoop.instance().start() # 挂起运行
客户端编程
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="javascript:WebSocketTest()">run websocket</a> <div id="messages" style="height: 200px;background: black;color: #e0e0e0;"></div> <script type="text/javascript"> var messageContainer = document.getElementById('messages'); function WebSocketTest() { if ("WebSocket" in window){ messageContainer.innerHTML = '你的浏览器支持websocket'; var ws = new WebSocket('ws://localhost:8888/websocket?Id=12345'); ws.onopen = function () { ws.send("message to send"); }; ws.onmessage = function (evt) { var received_msg = evt.data; messageContainer.innerHTML = messageContainer.innerHTML+"<br/>message is received:"+received_msg; }; ws.onclose = function () { messageContainer.innerHTML = messageContainer.innerHTML+"<br/>链接已经关闭"; }; } else { messageContainer.innerHTML = '你的浏览器不支持websocket'; } } </script> </body> </html>