zoukankan      html  css  js  c++  java
  • websocket原理

     WebSocket协议是基于TCP的一种新的协议。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。

    1、启动客户端

    <script type="text/javascript">
        var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");
        ...
    </script>
    

    2、启动服务端并建立连接

    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', 8002))
    sock.listen(5)
    # 获取客户端socket对象
    conn, address = sock.accept()
    # 获取客户端的【握手】信息
    data = conn.recv(1024)
    ...
    ...
    ...
    conn.send('响应【握手】信息')
    

      请求和响应的握手过程:

        - 从请求握手的信息中提取Sec-WebSocket-Key

        - 利用sha1和base64对magic_string(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)和Sec-WebSocket-Key进行加密

        - 将加密结果响应给客户端

      代码如下:

    import socket
    import base64
    import hashlib
     
    def get_headers(data):
        """
        将请求头格式化成字典
        :param data:
        :return:
        """
        header_dict = {}
        data = str(data, encoding='utf-8')
     
        for i in data.split('
    '):
            print(i)
        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', 8002))
    sock.listen(5)
     
    conn, address = sock.accept()
    data = conn.recv(1024)
    headers = get_headers(data) # 提取请求头信息
    # 对请求头中的sec-websocket-key进行加密
    response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
          "Upgrade:websocket
    " 
          "Connection: Upgrade
    " 
          "Sec-WebSocket-Accept: %s
    " 
          "WebSocket-Location: ws://%s%s
    
    "
    magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    value = headers['Sec-WebSocket-Key'] + magic_string
    ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
    response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
    # 响应【握手】信息
    conn.send(bytes(response_str, encoding='utf-8'))
    View Code

    3、客户端和服务端收发数据

      客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。客户端的JavaScript类库封装了【封包】和【解包】过程,但Socket服务端需要手动实现:

      第一步:获取客户端发送的数据【解包】

        【解包】的详细过程:

    The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We'll explain masking later. Note: You have to mask messages even when using a secure socket.RSV1-3 can be ignored, they are for extensions.
    
    The opcode field defines how to interpret the payload data: 0x0 for continuation, 0x1 for text (which is always encoded in UTF-8), 0x2 for binary, and other so-called "control codes" that will be discussed later. In this version of WebSockets, 0x3 to 0x7 and 0xB to 0xF have no meaning.
    
    The FIN bit tells whether this is the last message in a series. If it's 0, then the server will keep listening for more parts of the message; otherwise, the server should consider the message delivered. More on this later.
    
    Decoding Payload Length
    
    To read the payload data, you must know when to stop reading. That's why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:
    
    Read bits 9-15 (inclusive) and interpret that as an unsigned integer. If it's 125 or less, then that's the length; you're done. If it's 126, go to step 2. If it's 127, go to step 3.
    Read the next 16 bits and interpret those as an unsigned integer. You're done.
    Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You're done.
    Reading and Unmasking the Data
    
    If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let's call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):
    
     
    
    var DECODED = "";
    for (var i = 0; i < ENCODED.length; i++) {
        DECODED[i] = ENCODED[i] ^ MASK[i % 4];
    }
    
     
    
    Now you can figure out what DECODED means depending on your application.
            0               1               2                3
     0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7  0 1 2 3 4 5 6 7 
    +-+-+-+-+-------+-+-------------+-------------------------------+
    |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         |
    +-------------------------------- - - - - - - - - - - - - - - - +
    

        代码如下:

    info = conn.recv(8096)
    
    # 用数据包的第二个字节,与127作与位运算,拿到前七位
    payload_len = info[1] & 127
    # 这七位在数据头部分成为payload,如果payload等于126,就要再扩展2个字节
    if payload_len == 126:
        extend_payload_len = info[2:4]
        mask = info[4:8]
        decoded = info[8:]
    # 如果等于127,就要再扩展8个字节
    elif payload_len == 127:
        extend_payload_len = info[2:10]
        mask = info[10:14]
        decoded = info[14:]
    # 如果小于等于125,那它就占这一个字节
    else:
        extend_payload_len = None
        mask = info[2:6]
        decoded = info[6:]
    
    # 当payload确定之后,再往后数4个字节,这4个字节成为masking key,再之后的内容就是接收到的数据部分了
    # 数据部分的每一字节都要和masking key作异或位运算,得出来的结果就是真实的数据内容
    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')
    print(body)

      第二步:向客户端发送数据【封包】

    def send_msg(conn, msg_bytes):
        """
        WebSocket服务端向客户端发送消息
        :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
        :param msg_bytes: 向客户端发送的字节
        :return: 
        """
        import struct
    
        token = b"x81"
        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

      第三步:客户端代码

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

      以上便利用Python完成了基于websocket的通信。

      它的原理就是利用对magic_string和Sec-WebSocket-Key进行加密来建立长连接,在收发数据时,利用payload和masking key来获取数据部分。

  • 相关阅读:
    论文复现的一些问题
    南京锐士方达猎头可拉黑
    Functional Ruby
    国内访问 Atom 源很慢 & 解决方案
    Linux 的硬链接与软链接
    Python小知识点(持续更新)
    MySQL小知识点(持续更新)
    Iterable Object, Iterator, Generator, Generator Iterator
    setTimeout函数在浏览器中和Node.js中的区别
    Several Python Tools/Utilities
  • 原文地址:https://www.cnblogs.com/value-code/p/8746753.html
Copyright © 2011-2022 走看看