WebSocket

WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。

一、The WebSocket Handshake握手过程

  1. Client Handshake Request ------客户端握手请求,客户端发送get请求,样例如下:   
1 GET /chat HTTP/1.1
2 Host: example.com:8000
3 Upgrade: websocket
4 Connection: Upgrade
5 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
6 Sec-WebSocket-Version: 13

  2.Server Handshake Response------服务端握手响应

    •   从请求【握手】信息中提取 Sec-WebSocket-Key
    •   利用magic_string(258EAFA5-E914-47DA-95CA-C5AB0DC85B11) 和 Sec-WebSocket-Key 进行sha-1加密,再进行hash的base64编码

  将加密结果响应给客户端

1 HTTP/1.1 101 Switching Protocols 2 Upgrade: websocket 3 Connection: Upgrade 4 Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

一旦服务端发送类似上面的头部信息,握手完成,可以相互发送消息。

  3.服务端跟踪客户端的socket。监听其连接状态

二、JavaScript类库实现客户端

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
    </div>
    <div id="content"></div>
  
<script type="text/javascript">
    var socket = new WebSocket("ws://127.0.0.1:8003/chatsocket");
  
    socket.onopen = function () {
        /* 与服务器端连接成功后,自动执行 */
  
        var newTag = document.createElement('div');
        newTag.innerHTML = "【连接成功】";
        document.getElementById('content').appendChild(newTag);
    };
  
    socket.onmessage = function (event) {
        /* 服务器端向客户端发送数据时,自动执行 */
        var response = event.data;
        var newTag = document.createElement('div');
        newTag.innerHTML = response;
        document.getElementById('content').appendChild(newTag);
    };
  
    socket.onclose = function (event) {
        /* 服务器端主动断开连接时,自动执行 */
        var newTag = document.createElement('div');
        newTag.innerHTML = "【关闭连接】";
        document.getElementById('content').appendChild(newTag);
    };
  
    function sendMsg() {
        var txt = document.getElementById('txt');
        socket.send(txt.value);
        txt.value = "";
    }
    function closeConn() {
        socket.close();
        var newTag = document.createElement('div');
        newTag.innerHTML = "【关闭连接】";
        document.getElementById('content').appendChild(newTag);
    }
  
</script>
</body>
</html>
WebSocket客户端
# WebSocket         对象            提供到远程主机的双向通道。
# close             方法            关闭websocket。
# send              方法            使用websocket 发送数据到服务器。
# binaryType        属性            由 onmessage 接收的二进制数据格式。
# bufferedAmount    属性            使用 send 的已排队的数据字节数。
# extensions        属性            报告服务器所选中的扩展名。
# onclose           属性            当套接字关闭时调用的事件处理程序。
# onerror           属性            当出现错误时调用的事件处理程序。
# onmessage         属性            通知接收到消息的事件处理程序。
# onopen            属性            当 websocket 已连接时调用的事件处理程序。
# protocol          属性            报告服务器所选中的协议。
# readyState        属性            报告 websocket 连接的状态。
# url               属性            报告套接字的当前 URL。
更多属性和方法

三、Tornado的WebSocket实现聊天室

import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocket


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')


class ChatHandler(tornado.websocket.WebSocketHandler):
    # 用户存储当前聊天室用户
    waiters = set()
    # 用于存储历时消息
    messages = []

    def open(self):
        """
        客户端连接成功时,自动执行
        :return:
        """
        ChatHandler.waiters.add(self)
        uid = str(uuid.uuid4())
        self.write_message(uid)

        for msg in ChatHandler.messages:
            content = self.render_string('message.html', **msg)
            self.write_message(content)

    def on_message(self, message):
        """
        客户端发送消息时,自动执行
        :param message:
        :return:
        """
        msg = json.loads(message)
        ChatHandler.messages.append(message)

        for client in ChatHandler.waiters:
            content = client.render_string('message.html', **msg)
            client.write_message(content)

    def on_close(self):
        """
        客户端关闭连接时,,自动执行
        :return:
        """
        ChatHandler.waiters.remove(self)


def run():
    settings = {
        'template_path': 'templates',
        'static_path': 'static',
    }
    application = tornado.web.Application([
        (r"/", IndexHandler),
        (r"/chat", ChatHandler),
    ], **settings)
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    run()
Tornado的WS服务端
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python聊天室</title>
</head>
<body>
    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
    </div>
    <div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">

    </div>

    <script src="/static/jquery-2.1.4.min.js"></script>
    <script type="text/javascript">
        $(function () {
            wsUpdater.start();
        });

        var wsUpdater = {
            socket: null,
            uid: null,
            start: function() {
                var url = "ws://127.0.0.1:8888/chat";
                wsUpdater.socket = new WebSocket(url);
                wsUpdater.socket.onmessage = function(event) {
                    console.log(event);
                    if(wsUpdater.uid){
                        wsUpdater.showMessage(event.data);
                    }else{
                        wsUpdater.uid = event.data;
                    }
                }
            },
            showMessage: function(content) {
                $('#container').append(content);
            }
        };

        function sendMsg() {
            var msg = {
                uid: wsUpdater.uid,
                message: $("#txt").val()
            };
            wsUpdater.socket.send(JSON.stringify(msg));
        }

</script>

</body>
</html>
index页面,JS实现WS客户端

 

posted @ 2017-06-06 14:19  skiler  阅读(162)  评论(0)    收藏  举报