zoukankan      html  css  js  c++  java
  • WebSocket

    WebSocket

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

     pip3 install gevent-websocket

    1.HTTP协议: http://www.baidu.com
        一次请求 一次响应 断开
        无状态 - 你曾经来过 session or cookie 
        当客户端(YWB)发起请求时 服务器才能定位到客户端(YWB) 服务器才能给客户端(YWB)回消息
        ?:服务器有一个要送给 YWB 的消息
        !:等待客户端(YWB)连接上来,再返回响应的时候,将消息一次性,返回
        
        轮询:94 - 2002 - 调制解调器通过电话线 - 7.5kbps - 3KB/s / 52kbps - 12KB/s / 800快 / P4 / 1.6 GHZ
            每一秒钟发送600次请求 一次收取100条数据
            浪费资源 - 服务器处理资源 - 客户端发请求浪费处理资源 - 序列化消息浪费处理资源
            优点:消息基本实时
            缺点:双资源浪费
            ICO : 轮询 - 就是QQ - OICQ - 2000
        
        长轮询:2000 - 今天 - ADSL 宽带 - 128kbps - 50KB/s / 256 - 1M - 2M - 4M 1MB/s / - 10M - 100M 
            客户端发送一个请求 - 服务器接收请求 - 不返回 - 阻塞等待客户端的消息 - 如果有消息了 - 返回给客户端 - 断开
            瞬间重连 - 客户端发送一个请求
            服务器底层小循环,占用极小资源
            缺点:断开重连次数过多
            优点:节省了部分资源,数据实时性略差
            
    2.WebSocket协议: ws://baidu.com
        一次请求 - 服务器收到请求 开始和客户端握手 - 保持长连接 - 数据实时 - 连接永远保持
        优点:长连接 并且不影响 收发请求
        缺点:连接保持,是需要资源的
    View Code

    1. 启动服务端

    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)
    # 等待用户连接
    conn, address = sock.accept()
    ...
    ...
    ...

    启动Socket服务器后,等待用户【连接】,然后进行收发数据。

    2. 客户端连接

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

    当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!

    3. 建立连接【握手】

    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
    • 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
    • 将加密结果响应给客户端

    注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

    请求【握手】信息为:

    GET /chatsocket HTTP/1.1
    Host: 127.0.0.1:8002
    Connection: Upgrade
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade: websocket
    Origin: http://localhost:63342
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    ...
    ...
    View Code

    提取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('\r\n'):
            print(i)
        header, body = data.split('\r\n\r\n', 1)
        header_list = header.split('\r\n')
        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)
    # 获取客户端socket对象
    conn, address = sock.accept()
    # 获取客户端的【握手】信息
    data = conn.recv(1024)
    print(data)
    
    """
    b'GET /ws HTTP/1.1\r\n
    Host: 127.0.0.1:9527\r\n
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0\r\n
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\n
    Accept-Encoding: gzip, deflate\r\n
    Sec-WebSocket-Version: 13\r\n
    Origin: http://localhost:63342\r\n
    Sec-WebSocket-Extensions: permessage-deflate\r\n
    Sec-WebSocket-Key: jocLOLLq1BQWp0aZgEWL5A==\r\n
    %sCookie: session=6f2bab18-2dc4-426a-8f06-de22909b967b\r\n
    Connection: keep-alive, Upgrade\r\n
    Pragma: no-cache\r\n
    Cache-Control: no-cache\r\n
    Upgrade: websocket\r\n\r\n'
    """
    
    headers = get_headers(data)  # 提取请求头信息
    # 对请求头中的sec-websocket-key进行加密
    response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
                   "Upgrade:websocket\r\n" \
                   "Connection: Upgrade\r\n" \
                   "Sec-WebSocket-Accept: %s\r\n" \
                   "WebSocket-Location: ws://%s%s\r\n\r\n"
    
    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'))
    
    
    while True:
        msg = conn.recv(8096)
        print(msg)
    View Code

    4.客户端和服务端收发数据

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

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

    # hashstr = conn.recv(8096)
    # b'\x81\x83\xceH\xb6\x85\xffz\x85'
    
    hashstr = b'\x81\x83\xceH\xb6\x85\xffz\x85'
    # b'\x81    \x83    \xceH\xb6\x85\xffz\x85'
    
    # 将第二个字节也就是 \x83 第9-16位 进行与127进行位运算
    payload = hashstr[1] & 127
    
    if payload == 127:
        extend_payload_len = hashstr[2:10]
        mask = hashstr[10:14]
        decoded = hashstr[14:]
    # 当位运算结果等于127时,则第3-10个字节为数据长度
    # 第11-14字节为mask 解密所需字符串
    # 则数据为第15字节至结尾
    
    if payload == 126:
        extend_payload_len = hashstr[2:4]
        mask = hashstr[4:8]
        decoded = hashstr[8:]
    # 当位运算结果等于126时,则第3-4个字节为数据长度
    # 第5-8字节为mask 解密所需字符串
    # 则数据为第9字节至结尾
    
    
    if payload <= 125:
        extend_payload_len = None
        mask = hashstr[2:6]
        decoded = hashstr[6:]
    
    # 当位运算结果小于等于125时,则这个数字就是数据的长度
    # 第3-6字节为mask 解密所需字符串
    # 则数据为第7字节至结尾
    
    str_byte = bytearray()
    
    for i in range(len(decoded)):
        byte = decoded[i] ^ mask[i % 4]
        str_byte.append(byte)
    
    print(str_byte.decode("utf8"))
    基于Python实现解包过程(未实现长内容)

    解包详细过程: 

    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 ...                |
    +---------------------------------------------------------------+
    View Code

    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:

    1. 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.
    2. Read the next 16 bits and interpret those as an unsigned integer. You're done.
    3. 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.

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

    import struct
    msg_bytes = "hello".encode("utf8")
    token = b"\x81"
    length = len(msg_bytes)
    
    if length < 255:
        token += struct.pack("B", length)
    elif length == 65535:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)
    
    msg = token + msg_bytes
    
    print(msg)
    View Code

    5. 基于Python实现简单示例

    a. 基于Python socket实现的WebSocket服务端:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    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('\r\n\r\n', 1)
        header_list = header.split('\r\n')
        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"
        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
     
     
    def run():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(('127.0.0.1', 8003))
        sock.listen(5)
     
        conn, address = sock.accept()
        data = conn.recv(1024)
        headers = get_headers(data)
        response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
                       "Upgrade:websocket\r\n" \
                       "Connection:Upgrade\r\n" \
                       "Sec-WebSocket-Accept:%s\r\n" \
                       "WebSocket-Location:ws://%s%s\r\n\r\n"
     
        value = headers['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        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'))
     
        while True:
            try:
                info = conn.recv(8096)
            except Exception as e:
                info = None
            if not info:
                break
            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')
            send_msg(conn,body.encode('utf-8'))
     
        sock.close()
     
    if __name__ == '__main__':
        run()
    View Code

    b. 利用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>
    View Code

    6. 基于flask+geventwebsocket

    # websocket
    from geventwebsocket.server import WSGIServer  # 我要WSGI为我提供服务
    from geventwebsocket.handler import WebSocketHandler  # WSGI 遇到WS协议的时候,处理方式
    from geventwebsocket.websocket import WebSocket  # 语法提示
    
    # 基于Flask+geventwebsocket
    from flask import Flask, request,render_template
    
    app = Flask(__name__)
    
    ##多人聊天
    user_socket_list=[]
    
    @app.route("/ws")
    def my_ws_func():
        # print(dir(request.environ))
        user_socket = request.environ.get("wsgi.websocket") #type:WebSocket
        # print(user_socket) #<geventwebsocket.websocket.WebSocket object at 0x10972fe18>
        user_socket_list.append(user_socket)
        while 1:
            msg = user_socket.receive() #等待接收客户端发送过来的消息
            for use in user_socket_list:
                # #看不到自己的发送信息
                # if use==user_socket:
                #     continue
                try:
                    use.send(msg)
                except:
                    continue
            # print(msg)
            # user_socket.send(msg)
    
    @app.route("/group_chart")
    def group_chart():
        return render_template("group_chart.html")
    
    
    """
    ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
    """
    
    if __name__ == '__main__':
        # app.run()
        http_serv = WSGIServer(("192.168.0.103", 3721), application=app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    
    ##http://192.168.0.103:3721/group_chart
    
    
    
    ##templates/group_chart.html
    """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <p>发送消息:<input type="text" id="message">
        <button onclick="send_msg()">发送</button>
    </p>
    <div id="message_list"></div>
    </body>
    <script>
        var ws = new WebSocket("ws://192.168.0.103:3721/ws");
        //当ws收到消息时执行 onmessage
        ws.onmessage = function (event) {
            console.log(event.data);
            var ptag = document.createElement("p");
            ptag.innerText = event.data;
            var divtag = document.getElementById("message_list");
            divtag.appendChild(ptag);
        };
    
        function send_msg() {
            var msg = document.getElementById("message").value;
            ws.send(msg);
            document.getElementById("message").value='';
        }
    
    
    </script>
    </html>
    """
    群聊
    # websocket
    from geventwebsocket.server import WSGIServer  # 我要WSGI为我提供服务
    from geventwebsocket.handler import WebSocketHandler  # WSGI 遇到WS协议的时候,处理方式
    from geventwebsocket.websocket import WebSocket  # 语法提示
    
    # 基于Flask+geventwebsocket
    from flask import Flask, request,render_template
    
    app = Flask(__name__)
    
    
    ##单聊
    import json
    user_socket_dict={}
    """
    'tom':<geventwebsocket.websocket.WebSocket object at 0x10972fe18>,
    'rose':<geventwebsocket.websocket.WebSocket object at 0x10972fe18>
    """
    
    @app.route("/wsone/<nickname>")
    def my_func(nickname):
        user_socket = request.environ.get("wsgi.websocket") #type:WebSocket
        user_socket_dict[nickname]=user_socket
        while 1:
            msg = user_socket.receive() #等待接收客户端发送过来的消息
            msg = json.loads(msg)
            """
            {
            to_user:
            from_user:
            message:""
            }
            """
            print(msg)
            to_user_socket = user_socket_dict.get(msg.get('to_user'))
            to_user_socket.send(json.dumps(msg))
    
    
    @app.route("/one_chart")
    def group_chart():
        return render_template("one_chart.html")
    
    """
    ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
    """
    
    if __name__ == '__main__':
        # app.run()
        http_serv = WSGIServer(("192.168.0.103", 3721), application=app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    
    ##http://192.168.0.103:3721/one_chart 都登陆以后聊天
    
    
    
    #templates/one_chart.html
    """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <p>登陆 <input type="text" id="nick"><button onclick="login()">登陆</button></p>
    <p>给:<input type="text" id="to_user"></p>
    <p>发送消息:<input type="text" id="message">
        <button onclick="send_msg()">发送</button>
    </p>
    <div id="message_list"></div>
    </body>
    <script>
    
        var ws = null;
    
        function send_msg() {
            var msg = document.getElementById("message").value;
            var to_user =document.getElementById('to_user').value;
            var nick = document.getElementById('nick').value;
            var msg_obj={
                to_user:to_user,
                from_user:nick,
                msg:msg,
            };
            var msg_json = JSON.stringify(msg_obj);
            ws.send(msg_json);
        }
    
        function login() {
            var nick = document.getElementById('nick').value;
            ws = new WebSocket("ws://192.168.0.103:3721/wsone/"+nick);
            //当ws收到消息时执行 onmessage
            ws.onmessage = function (event) {
                console.log(event.data);
                data_obj=JSON.parse(event.data);
                var ptag = document.createElement("p");
    
                ptag.innerText = data_obj.from_user+':'+ data_obj.msg;
    
                var divtag = document.getElementById("message_list");
                divtag.appendChild(ptag);
            };
    
        }
    
    
    </script>
    </html>
    """
    单聊
    import socket, base64, hashlib
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 3721))
    sock.listen(5)
    # 获取客户端socket对象
    conn, address = sock.accept()
    # 获取客户端的【握手】信息
    data = conn.recv(1024)
    print(data)
    """
    b'GET / HTTP/1.1\r\n
    Host: 127.0.0.1:3721\r\n
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0\r\n
    Accept: */*\r\nAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\n
    Accept-Encoding: gzip, deflate\r\n
    Sec-WebSocket-Version: 13\r\n
    Origin: http://localhost:63342\r\n
    Sec-WebSocket-Extensions: permessage-deflate\r\n
    Sec-WebSocket-Key: 9hCIuQr2e0nm9/5ZfpihZg==\r\n
    Connection: keep-alive, Upgrade\r\n
    Pragma: no-cache\r\n
    Cache-Control: no-cache\r\n
    Upgrade: websocket\r\n\r\n'
    """
    
    # magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
    magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    
    # 取出Sec-WebSocket-Key
    def get_headers(data):
        header_dict = {}
        header_str = data.decode("utf8")
        for i in header_str.split("\r\n"):
            if str(i).startswith("Sec-WebSocket-Key"):
                header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip()
        return header_dict
    
    
    # def get_header(data):
    #     """
    #      将请求头格式化成字典
    #      :param data:
    #      :return:
    #      """
    #     header_dict = {}
    #     data = str(data, encoding='utf-8')
    #
    #     header, body = data.split('\r\n\r\n', 1)
    #     header_list = header.split('\r\n')
    #     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
    #
    
    
    headers = get_headers(data)  # 提取请求头信息
    # 对请求头中的sec-websocket-key进行加密
    value = headers['Sec-WebSocket-Key'] + magic_string
    print(value)
    ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
    print(ac)
    # 响应
    response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
                   "Upgrade:websocket\r\n" \
                   "Connection: Upgrade\r\n" \
                   "Sec-WebSocket-Accept: %s\r\n" \
                   "WebSocket-Location: ws://127.0.0.1:3721\r\n\r\n"
    
    response_str = response_tpl % (ac.decode('utf-8'))
    # 响应【握手】信息
    conn.send(response_str.encode("utf8"))
    
    while True:
        msg = conn.recv(8096)
        print(msg)
    
    
    ##
    """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    </body>
    <script>
        var ws = new WebSocket("ws://127.0.0.1:3721");
        ws.onmessage = function (event) {
            console.log(event.data);
        };
    </script>
    </html>
    """
    WebSocket
    # b'\x81\x83\xceH\xb6\x85\xffz\x85'
    
    hashstr = b'\x81\x83\xceH\xb6\x85\xffz\x85'
    # b'\x81    \x83    \xceH\xb6\x85\xffz\x85'
    
    # 将第二个字节也就是 \x83 第9-16位 进行与127进行位运算
    payload = hashstr[1] & 127
    print(payload)
    if payload == 127:
        extend_payload_len = hashstr[2:10]
        mask = hashstr[10:14]
        decoded = hashstr[14:]
    # 当位运算结果等于127时,则第3-10个字节为数据长度
    # 第11-14字节为mask 解密所需字符串
    # 则数据为第15字节至结尾
    
    if payload == 126:
        extend_payload_len = hashstr[2:4]
        mask = hashstr[4:8]
        decoded = hashstr[8:]
    # 当位运算结果等于126时,则第3-4个字节为数据长度
    # 第5-8字节为mask 解密所需字符串
    # 则数据为第9字节至结尾
    
    
    if payload <= 125:
        extend_payload_len = None
        mask = hashstr[2:6]
        decoded = hashstr[6:]
    
    # 当位运算结果小于等于125时,则这个数字就是数据的长度
    # 第3-6字节为mask 解密所需字符串
    # 则数据为第7字节至结尾
    
    str_byte = bytearray() #流的数组
    
    for i in range(len(decoded)):
        byte = decoded[i] ^ mask[i % 4]
        str_byte.append(byte)
    
    print(str_byte.decode("utf8"))
    解密
    import struct
    msg_bytes = "hello".encode("utf8")
    token = b"\x81"
    length = len(msg_bytes)
    
    if length <= 254:
        token += struct.pack("B", length)
    elif length <= 65535:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)
    
    msg = token + msg_bytes
    
    print(msg)
    加密

    7. 基于Tornado框架实现Web聊天室

    Tornado是一个支持WebSocket的优秀框架,其内部原理正如1~5步骤描述,当然Tornado内部封装功能更加完整。

    以下是基于Tornado实现的聊天室示例:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    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()
    app.py
    <!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.html

    示例源码下载

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

    Flask+geventwebsocket
  • 相关阅读:
    JS-字符串截取方法slice、substring、substr的区别
    Vue中computed和watch的区别
    Vue响应式原理及总结
    JS实现深浅拷贝
    JS中new操作符源码实现
    点击页面出现爱心效果
    js判断对象是否为空对象的几种方法
    深入浅出js实现继承的7种方式
    es6-class
    详解 ESLint 规则,规范你的代码
  • 原文地址:https://www.cnblogs.com/bubu99/p/11324666.html
Copyright © 2011-2022 走看看