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>

 

posted @ 2017-06-26 09:17  Erick-LONG  阅读(826)  评论(0编辑  收藏  举报