zoukankan      html  css  js  c++  java
  • WebSocket学习

    客户端连接

    客户端

    <script>
        var socket = new WebSocket("ws://127.0.0.1:8000/chat");
    </script>

    服务端

    import socket
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8000))
    sock.listen(5)
    
    conn, address = sock.accept()
    print('有链接过来。。。')
    data = conn.recv(1024)
    print(data)
    """
    GET /chat HTTP/1.1
    
    Host: 127.0.0.1:8000
    
    Connection: Upgrade
    
    Pragma: no-cache
    
    Cache-Control: no-cache
    
    Upgrade: websocket
    
    Origin: http://localhost:63342
    
    Sec-WebSocket-Version: 13
    
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
    
    Accept-Encoding: gzip, deflate, br
    
    Accept-Language: zh-CN,zh;q=0.9
    
    Sec-WebSocket-Key: CyiOUMgm4Qd6WyjLQ+YbrA==
    
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    
    
    """
    
    conn.send("响应【握手】信息".encode('utf-8'))
    conn.close()
    

    浏览器发起了连接请求,其中包含了握手信息,服务端要想握手成功达成连接,需要满足下面几个条件:

      - 首先获取客户端发来的 Sec-WebSocket-Key ,然后把这个值和 magic_string258EAFA5-E914-47DA-95CA-C5AB0DC85B11)连接起来。

      - 然后进行 SHA-1 hash 加密,最后再进行 base64 加密。

      - 最后得到的值赋值给 Sec-WebSocket-Accept 字段当做响应头发给客户端。

    响应头格式如下:

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

      

    建立连接(握手)

    客户端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
        <button onclick="connection_check()">check</button>
    
    <script>
        var socket = new WebSocket("ws://127.0.0.1:8000/chat");
    
        function connection_check() {
            if(0 == socket.readyState) {
                alert('尚未建立连接');
            }
            else if(1 == socket.readyState) {
                alert('连接已建立');
            }
            else if(2 == socket.readyState) {
                alert('连接正在进行关闭');
            }
            else {
                alert('连接已经关闭或者连接不能打开');
            }
        }
    
    
    </script>
    
    </body>
    </html>

    服务端

    import socket
    import hashlib
    import base64
    
    
    def get_headers(data):
        """
        将请求头格式化成字典
        :param data:
        :return:
        """
        header_dict = {}
        data = str(data, encoding='utf-8')
    
        header, body = data.split('
    
    ', 1)
        header_list = header.split('
    ')
        for i in range(0, len(header_list)):
            if i == 0:
                if len(header_list[i].split(' ')) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
            else:
                k, v = header_list[i].split(':', 1)
                header_dict[k] = v.strip()
        return header_dict
    
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8000))
    sock.listen(5)
    
    while True:
        conn, address = sock.accept()
        print('有链接过来。。。')
        data = conn.recv(1024)
    
        headers = get_headers(data)
        magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        str_concatenate = headers['Sec-WebSocket-Key']+magic_string
    
        swa = base64.b64encode(hashlib.sha1(bytes(headers['Sec-WebSocket-Key']+magic_string,encoding='utf-8')).digest())
        response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
            "Upgrade:websocket
    " 
            "Connection: Upgrade
    " 
            "Sec-WebSocket-Accept: %s
    " 
            "WebSocket-Location: ws://%s%s
    
    "
    
        print(response_tpl)
    
        response = response_tpl %(swa.decode('utf-8'),headers['Host'],headers['url'])
        conn.send(bytes(response,encoding='utf-8'))
    
    conn.close()
    

    服务端解析客户端发的数据【解包】

    客户端发来的 Frame Format

          0                   1                   2                   3
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         +-+-+-+-+-------+-+-------------+-------------------------------+
         |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
         |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
         |N|V|V|V|       |S|             |   (if payload len==126/127)   |
         | |1|2|3|       |K|             |                               |
         +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
         |     Extended payload length continued, if payload len == 127  |
         + - - - - - - - - - - - - - - - +-------------------------------+
         |                               |Masking-key, if MASK set to 1  |
         +-------------------------------+-------------------------------+
         | Masking-key (continued)       |          Payload Data         |
         +-------------------------------- - - - - - - - - - - - - - - - +
         :                     Payload Data continued ...                :
         + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
         |                     Payload Data continued ...                |
         +---------------------------------------------------------------+

    官方解释:

    Client: FIN=1, opcode=0x1, msg="hello"
    Server: (process complete message immediately) Hi.
    Client: FIN=0, opcode=0x1, msg="and a"
    Server: (listening, new message containing text started)
    Client: FIN=0, opcode=0x0, msg="happy new"
    Server: (listening, payload concatenated to previous message)
    Client: FIN=1, opcode=0x0, msg="year!"
    Server: (process complete message) Happy new year to you too!

    客户端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
        <input type="text" id="input_msg">
        <button id="send_msg">发送消息</button>
    
    <script>
        var socket = new WebSocket("ws://127.0.0.1:8000/chat");
        var send_btn = document.getElementById('send_msg');
        var input = document.getElementById('input_msg');
        send_btn.onclick = function() {
            console.log(input.value);
            if(1 == socket.readyState){
                socket.send(input.value);
                input.value = "";
            }else {
                alert('连接尚未建立');
            }
        }
    
    </script>
    
    </body>
    </html>

    服务端

    import socket
    import hashlib
    import base64
    
    
    def get_headers(data):
        """
        将请求头格式化成字典
        :param data:
        :return:
        """
        header_dict = {}
        data = str(data, encoding='utf-8')
    
        header, body = data.split('
    
    ', 1)
        header_list = header.split('
    ')
        for i in range(0, len(header_list)):
            if i == 0:
                if len(header_list[i].split(' ')) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
            else:
                k, v = header_list[i].split(':', 1)
                header_dict[k] = v.strip()
        return header_dict
    
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8000))
    sock.listen(5)
    
    conn, address = sock.accept()
    print('有链接过来。。。')
    data = conn.recv(1024)
    
    headers = get_headers(data)
    magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    str_concatenate = headers['Sec-WebSocket-Key']+magic_string
    
    swa = base64.b64encode(hashlib.sha1(bytes(headers['Sec-WebSocket-Key']+magic_string,encoding='utf-8')).digest())
    response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
        "Upgrade:websocket
    " 
        "Connection: Upgrade
    " 
        "Sec-WebSocket-Accept: %s
    " 
        "WebSocket-Location: ws://%s%s
    
    "
    
    response = response_tpl %(swa.decode('utf-8'),headers['Host'],headers['url'])
    conn.send(bytes(response,encoding='utf-8'))
    
    while True:
        info = conn.recv(8096)
        payload_len = info[1] & 127
        if payload_len == 126:
            extend_payload_len = info[2:4]
            mask = info[4:8]
            decoded = info[8:]
        elif payload_len == 127:
            extend_payload_len = info[2:10]
            mask = info[10:14]
            decoded = info[14:]
        else:
            extend_payload_len = None
            mask = info[2:6]
            decoded = info[6:]
    
        bytes_list = bytearray()
        for i in range(len(decoded)):
            chunk = decoded[i] ^ mask[i % 4]
            bytes_list.append(chunk)
        # body = str(bytes_list, encoding='utf-8')
        body = bytes_list.decode('utf-8')
        print(body)
    
    
    conn.close()
    

      

    服务端给客户端发送数据【封包】

    def send_msg(conn, msg_bytes):
        """
        WebSocket服务端向客户端发送消息
        :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
        :param msg_bytes: 向客户端发送的字节
        :return: 
        """
        import struct
    
        token = b"x81"  # 即 10000001,FIN置为1,opcode置为0x1
        length = len(msg_bytes)
        if length < 126:
            token += struct.pack("B", length)
        elif length <= 0xFFFF:
            token += struct.pack("!BH", 126, length)
        else:
            token += struct.pack("!BQ", 127, length)
    
        msg = token + msg_bytes
        conn.send(msg)
        return True

    服务端封包的时候不需要加密,即没有 MASK 字段。

    但是需要注意length的判断和字节序的问题

    基于Python简单实现的WebSocket示例

    服务端:

    import socket
    import base64
    import hashlib
    
    
    def get_headers(data):
        """
        将请求头格式化成字典
        :param data:
        :return:
        """
        header_dict = {}
        data = str(data, encoding='utf-8')
    
        header, body = data.split('
    
    ', 1)
        header_list = header.split('
    ')
        for i in range(0, len(header_list)):
            if i == 0:
                if len(header_list[i].split(' ')) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
            else:
                k, v = header_list[i].split(':', 1)
                header_dict[k] = v.strip()
        return header_dict
    
    
    def send_msg(conn, msg_bytes):
        """
        WebSocket服务端向客户端发送消息
        :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
        :param msg_bytes: 向客户端发送的字节
        :return:
        """
        import struct
    
        token = b"x81"    # 即 10000001,FIN置为1,opcode置为0x1
        length = len(msg_bytes)
        if length < 126:
            token += struct.pack("B", length)
        elif length <= 0xFFFF:
            # Read the next 16 bits and interpret those as an unsigned integer.
            token += struct.pack("!BH", 126, length)
        else:
            # Read the next 64 bits and interpret those as an unsigned integer.
            token += struct.pack("!BQ", 127, length)
    
        msg = token + msg_bytes
        conn.send(msg)
        return True
    
    def get_conn(ip='127.0.0.1',port=80):
        """
        准备接收客户端的请求并建立连接
        :param ip: 绑定的ip
        :param port: 监听的端口
        :return: 
        """
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((ip, port))
        sock.listen(5)
    
        conn, address = sock.accept()
        print("有连接到来...")
        data = conn.recv(1024)
        headers = get_headers(data)
        magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        str_completed = headers['Sec-WebSocket-Key'] + magic_string
    
        swa = base64.b64encode(hashlib.sha1(bytes(str_completed, encoding='utf-8')).digest())
        response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
                       "Upgrade:websocket
    " 
                       "Connection: Upgrade
    " 
                       "Sec-WebSocket-Accept: %s
    " 
                       "WebSocket-Location: ws://%s%s
    
    "
    
        response = response_tpl % (swa.decode('utf-8'), headers['Host'], headers['url'])
        conn.send(bytes(response, encoding='utf-8'))
        return sock,conn
    
    def close(sock):
        """
        关闭sock连接
        :param sock: 
        :return: 
        """
        sock.close()
    
    def run():
        """
        消息循环
        :return: 
        """
        sock,conn = get_conn()
    
        while True:
            try:
                info = conn.recv(8096)
            except Exception as e:
                info = None
            if not info:
                break
            # 位运算得到第二个字节后7个bit的值
            payload_len = info[1] & 127
            if payload_len == 126:
                extend_payload_len = info[2:4]
                mask = info[4:8]
                decoded = info[8:]
            elif payload_len == 127:
                extend_payload_len = info[2:10]
                mask = info[10:14]
                decoded = info[14:]
            else:
                extend_payload_len = None
                mask = info[2:6]
                decoded = info[6:]
    
            bytes_list = bytearray()
            # DECODED
            for i in range(len(decoded)):
                chunk = decoded[i] ^ mask[i % 4]
                bytes_list.append(chunk)
            body = str(bytes_list, encoding='utf-8')
            print("Client: %s" % body)
            send_msg(conn, ("服务端已收到: %s" % body).encode('utf-8'))
    
        close(sock)
    
    
    if __name__ == '__main__':
        run()

    客户端:

    <!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:80/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>
    

      

    基于Tornado框架实现Web聊天室

    示例来自:http://www.cnblogs.com/wupeiqi/articles/6558766.html

    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()

    index.html

    <!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>

    参考:http://www.cnblogs.com/wupeiqi/articles/6558766.html

    参考:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

    RFC 6455:https://tools.ietf.org/html/rfc6455

    WebSocket+Flask实现的实时投票和实时通知示例

    '''
    pip install gevent-websocket
    '''
    
    from flask import Flask,render_template,request,session,redirect,jsonify
    import uuid
    from geventwebsocket.handler import WebSocketHandler
    from gevent.pywsgi import WSGIServer
    import json
    
    
    app = Flask(__name__)
    app.secret_key = 'wnclwespcqwlkjaytcw'
    
    USERS = {
        '1':{'name':'小明','count':0},
        '2':{'name':'小强','count':0},
        '3':{'name':'小刚','count':0},
    }
    
    
    @app.before_request
    def before_request():
        allow_url = ['login','notify']
        if request.endpoint in allow_url:
            return None
        user_info = session.get('user_info')
        if user_info:
            return None
        return redirect('/login')
    
    
    @app.route('/login',methods=['GET','POST'])
    def login():
        if request.method == "GET":
            return render_template('login.html')
        else:
            uid = str(uuid.uuid4())
            session['user_info'] = {'id':uid,'name':request.form.get('user')}
            return redirect('/index')
    
    
    @app.route('/index')
    def index():
        return render_template('index.html',users=USERS)
    
    
    # 用来保存全部WS链接
    WS_DICT = {}
    
    
    @app.route('/notify')
    def notify():
        data = {'msg': '有新工单到来,请及时处理!', 'type': 'notify'}
    
        for k, v in WS_DICT.items():
            # 3. 向全部存活的客户端推送消息
            v.send(json.dumps(data))
    
    
        return "消息已推送"
    
    
    @app.route('/message')
    def message():
        if request.environ.get('wsgi.websocket'):
            # 根据 'wsgi.websocket' 这个字段是否为空来判断来源是 websocket 还是 http
            ws = request.environ['wsgi.websocket']
            if not ws:
                return "请使用WebSocket协议"
            # 1. 刚连接成功
            uid = session.get('user_info').get('id')
            WS_DICT[uid] = ws
    
            print('有连接到来, Name: %s from: %s' % (session['user_info'],ws))
            print(WS_DICT)
    
    
            from geventwebsocket.websocket import WebSocket
            while True:
                # 2. 等待用户发送消息,并接受
                message = ws.receive()
                # 如果客户端关闭浏览器,那么就会收到一个None,即 message=None
                if not message:
                    del WS_DICT[uid]
                    break
    
                old = USERS[message]['count']
                new = old + 1
                USERS[message]['count'] = new
    
                data = {'user':message,'count':new,'type':'vote'}
    
                for k,v in WS_DICT.items():
                    # 3. 向全部存活的客户端推送消息
                    v.send(json.dumps(data))
    
        return "Connected!"
    
    if __name__ == '__main__':
        http_server = WSGIServer(('127.0.0.1', 8000), app, handler_class=WebSocketHandler)
        http_server.serve_forever()
    

      

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form method="post" novalidate>
        <input type="text" name="user">
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>基于WebSocket+Flask的投票系统</h1>
        <a onclick="closeConn();" href="#">关闭连接</a>
    
        <!--
            <a onclick="createConn();" href="">创建连接</a>
            这样写会导致无法保持websocket连接
        -->
    
        <a onclick="createConn();" href="#">创建连接</a>
        <ul>
            {% for k,v in users.items() %}
                <li style="cursor:pointer;" id="user_{{k}}" ondblclick="vote('{{k}}')">{{v.name}} <span>{{v.count}}</span> </li>
            {% endfor %}
    
        </ul>
    
        <script src="{{ url_for('static',filename='jquery-3.3.1.min.js')}}"></script>
        <script>
    
            var socket = null;
    
            function socketInit() {
                socket.onopen = function () {
                    /* 与服务器端连接成功后,自动执行 */
                    alert('与服务器端握手成功,可以开始投票。');
                };
    
                socket.onmessage = function (event) {
                    /* 服务器端向客户端发送数据时,自动执行 */
                    var response = JSON.parse(event.data); // {'user':1,'count':new,'type':'vote'}
                    if('vote' == response['type'])
                    {
                        var nid = '#user_' + response.user;
                        $(nid).find('span').text(response.count);
                    }
                    else if('notify' == response['type'])
                    {
                        alert(response['msg']);
                    }
    
                };
    
                socket.onclose = function (event) {
                    /* 服务器端主动断开连接时,自动执行 */
                    alert('服务器端已经断开WebSocket链接。');
                };
    
            }
            function vote(id) {
                if(socket != null)
                {
                    socket.send(id);
                }
                else
                {
                    alert('请先建立WebSocket链接');
                }
            }
            function closeConn() {
                socket.close()
            }
            function createConn() {
                socket = new WebSocket("ws://127.0.0.1:8000/message");
                socketInit();
            }
        </script>
    </body>
    </html>
    

      

  • 相关阅读:
    HDU 1058 Humble Numbers
    HDU 1160 FatMouse's Speed
    HDU 1087 Super Jumping! Jumping! Jumping!
    HDU 1003 Max Sum
    HDU 1297 Children’s Queue
    UVA1584环状序列 Circular Sequence
    UVA442 矩阵链乘 Matrix Chain Multiplication
    DjangoModels修改后出现You are trying to add a non-nullable field 'download' to book without a default; we can't do that (the database needs something to populate existing rows). Please select a fix:
    opencv做的简单播放器
    c++文件流输入输出
  • 原文地址:https://www.cnblogs.com/standby/p/8595588.html
Copyright © 2011-2022 走看看