美文网首页
WebSocket协议简介

WebSocket协议简介

作者: 诺之林 | 来源:发表于2020-03-25 15:10 被阅读0次

    协议

    • 双端实时通信

    • 基于TCP协议

    • 通过HTTP握手

    详细可以参考The WebSocket Protocol

    握手

    • HTTP Request
    GET / HTTP/1.1
    
    Connection: Upgrade
    
    Upgrade: websocket
    
    Sec-WebSocket-Key: b217JVMPYLsf0xXULygwPg==
    
    • HTTP Response
    HTTP/1.1 101 Switching Protocols
    
    Connection: Upgrade
    
    Upgrade: websocket
    
    Sec-WebSocket-Accept: HEzw6T2Rtwwtdask3X7uMvi5uSw=
    

    详细可以参考编写 WebSocket 服务器

    测试

    image.png

    实现

    vim websocket.py
    
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # https://docs.python.org/3.7/library/socketserver.html#module-socketserver
    
    
    import struct
    from base64 import b64encode
    from hashlib import sha1
    from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
    
    '''
    +-+-+-+-+-------+-+-------------+-------------------------------+
     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  |
    + - - - - - - - - - - - - - - - +-------------------------------+
    |                     Payload Data continued ...                |
    +---------------------------------------------------------------+
    '''
    
    FIN    = 0x80
    OPCODE = 0x0f
    MASKED = 0x80
    PAYLOAD_LEN = 0x7f
    PAYLOAD_LEN_EXT16 = 0x7e
    PAYLOAD_LEN_EXT64 = 0x7f
    
    OPCODE_CONTINUATION = 0x0
    OPCODE_TEXT         = 0x1
    OPCODE_BINARY       = 0x2
    OPCODE_CLOSE_CONN   = 0x8
    OPCODE_PING         = 0x9
    OPCODE_PONG         = 0xA
    
    
    class WebsocketServer(ThreadingMixIn, TCPServer):
        allow_reuse_address = True
        daemon_threads = True
    
        clients = []
        id_counter = 0
    
        def __init__(self, host='127.0.0.1', port=9000):
            TCPServer.__init__(self, (host, port), WebSocketHandler)
            self.port = self.socket.getsockname()[1]
    
        def run(self):
            try:
                self.serve_forever()
            except KeyboardInterrupt:
                self.server_close()
            except Exception as e:
                exit(1)
    
        def send_message(self, client, msg):
            self._unicast(client, msg)
    
        def send_message_to_all(self, msg):
            for client in self.clients:
                self._unicast(client, msg)
    
        def set_callbacks(self, client_connected_cb, client_disconnected_cb, message_received_cb):
            self.client_connected_cb = client_connected_cb
            self.client_disconnected_cb = client_disconnected_cb
            self.message_received_cb = message_received_cb
    
        def client_connected(self, handler):
            self.id_counter += 1
            client = {
                'id': self.id_counter,
                'handler': handler,
                'address': handler.client_address
            }
            self.clients.append(client)
            self.client_connected_cb(client, self)
    
        def client_disconnected(self, handler):
            client = self._handler_to_client(handler)
            self.clients.remove(client)
            self.client_disconnected_cb(client, self)
    
        def message_received(self, handler, msg):
            self.message_received_cb(self._handler_to_client(handler), self, msg)
    
        def ping_received(self, handler, msg):
            handler.send_pong(msg)
    
        def pong_received(self, handler, msg):
            pass
    
        def _unicast(self, client, msg):
            client['handler'].send_message(msg)
    
        def _handler_to_client(self, handler):
            for client in self.clients:
                if client['handler'] == handler:
                    return client
    
    class WebSocketHandler(StreamRequestHandler):
    
        def __init__(self, socket, addr, server):
            StreamRequestHandler.__init__(self, socket, addr, server)
            self.server = server
    
        def setup(self):
            StreamRequestHandler.setup(self)
            self.keep_alive = True
            self.handshake_done = False
    
        def handle(self):
            while self.keep_alive:
                if not self.handshake_done:
                    self._handshake()
                else:
                    self._read_message()
    
        def finish(self):
            self.server.client_disconnected(self)
    
        def send_message(self, message):
            self._send_text(message)
    
        def send_pong(self, message):
            self._send_text(message, OPCODE_PONG)
    
        def _handshake(self):
            headers = {}
            http_get = self.rfile.readline().decode().strip()
            assert http_get.upper().startswith('GET')
            while True:
                header = self.rfile.readline().decode().strip()
                if not header:
                    break
                key, value = header.split(':', 1)
                headers[key.lower().strip()] = value.strip()
    
            try:
                assert headers['upgrade'].lower() == 'websocket'
            except AssertionError:
                self.keep_alive = False
                return
    
            try:
                key = headers['sec-websocket-key']
            except KeyError:
                self.keep_alive = False
                return
    
            response = self.make_handshake_response(key)
            self.handshake_done = self.request.send(response.encode())
            self.server.client_connected(self)
    
        def _read_message(self):
            try:
                b1, b2 = self.rfile.read(2)
            except ConnectionResetError as e:
                if e.errno == errno.ECONNRESET:
                    self.keep_alive = 0
                    return
                b1, b2 = 0, 0
            except ValueError as e:
                b1, b2 = 0, 0
    
            fin    = b1 & FIN
            opcode = b1 & OPCODE
            masked = b2 & MASKED
            payload_length = b2 & PAYLOAD_LEN
    
            if not MASKED:
                self.keep_alive = 0
                return
    
            if opcode == OPCODE_TEXT:
                opcode_handler = self.server.message_received
            elif opcode == OPCODE_PING:
                opcode_handler = self.server.ping_received
            elif opcode == OPCODE_PONG:
                opcode_handler = self.server.pong_received
            else:
                self.keep_alive = 0
                return
    
            if payload_length == 126:
                payload_length = struct.unpack(">H", self.rfile.read(2))[0]
            elif payload_length == 127:
                payload_length = struct.unpack(">Q", self.rfile.read(8))[0]
    
            masks = self.rfile.read(4)
            message_bytes = bytearray()
            for message_byte in self.rfile.read(payload_length):
                message_byte ^= masks[len(message_bytes) % 4]
                message_bytes.append(message_byte)
            opcode_handler(self, message_bytes.decode('utf8'))
    
        def _send_text(self, message, opcode=OPCODE_TEXT):
            header  = bytearray()
            payload = message.encode('UTF-8')
    
            payload_length = len(payload)
            if payload_length <= 125:
                header.append(FIN | opcode)
                header.append(payload_length)
            elif payload_length >= 126 and payload_length <= 65535:
                header.append(FIN | opcode)
                header.append(PAYLOAD_LEN_EXT16)
                header.extend(struct.pack(">H", payload_length))
            elif payload_length < 18446744073709551616:
                header.append(FIN | opcode)
                header.append(PAYLOAD_LEN_EXT64)
                header.extend(struct.pack(">Q", payload_length))
    
            self.request.send(header + payload)
    
        @classmethod
        def make_handshake_response(cls, key):
            return \
              'HTTP/1.1 101 Switching Protocols\r\n'\
              'Upgrade: websocket\r\n'              \
              'Connection: Upgrade\r\n'             \
              'Sec-WebSocket-Accept: %s\r\n'        \
              '\r\n' % cls.calculate_response_key(key)
    
        @classmethod
        def calculate_response_key(cls, key):
            GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
            hash = sha1(key.encode() + GUID.encode())
            response_key = b64encode(hash.digest()).strip()
            return response_key.decode('ASCII')
    
    vim server.py
    
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    
    from websocket import WebsocketServer
    
    
    def client_connected_cb(client, server):
        print("[客户端连接] id=%d" % client['id'])
        server.send_message_to_all("[服务端消息] 客户端连接 id=%d" % client['id'])
    
    def client_disconnected_cb(client, server):
        print("[客户端断开] id=%d" % client['id'])
        server.send_message_to_all("[服务端消息] 客户端断开 id=%d" % client['id'])
    
    def message_received_cb(client, server, message):
        print("[客户端消息] id=%d message=%s" % (client['id'], message))
    
    
    server = WebsocketServer()
    server.set_callbacks(client_connected_cb, client_disconnected_cb, message_received_cb)
    server.run()
    
    vim client.html
    
    <html>
    
    <head>
        <script type="text/javascript">
            var ws;
            function init() {
                ws = new WebSocket("ws://localhost:9000/");
                ws.onopen = function () {
                    output("[连接]");
                };
                ws.onmessage = function (e) {
                    output("[消息] => " + e.data);
                };
                ws.onclose = function () {
                    output("[关闭]");
                };
                ws.onerror = function (e) {
                    output("[错误] => " + e);
                };
            }
            function onSubmit() {
                var input = document.getElementById("input");
                ws.send(input.value);
                output("[发送] => " + input.value);
                input.value = "";
                input.focus();
            }
            function onCloseClick() {
                ws.close();
            }
            function output(str) {
                var log = document.getElementById("log");
                var escaped = str.replace(/&/, "&amp;").replace(/</, "&lt;").
                    replace(/>/, "&gt;").replace(/"/, "&quot;"); // "
                log.innerHTML = escaped + "<br>" + log.innerHTML;
            }
        </script>
    </head>
    
    <body onload="init();">
        <form onsubmit="onSubmit(); return false;">
            <input type="text" id="input">
            <input type="submit" value="发送">
            <button onclick="onCloseClick(); return false;">关闭</button>
        </form>
        <div id="log"></div>
    </body>
    
    </html>
    
    • 启动服务端 => python server.py

    • 启动客户端 => 浏览器打开client.html

    完整代码参考websocket-server

    参考

    相关文章

      网友评论

          本文标题:WebSocket协议简介

          本文链接:https://www.haomeiwen.com/subject/pshvkhtx.html