• WebSocket


    一 Websoket简介

    WebSocket 是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。
    WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
    在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

    轮询:

    1. 不断向服务器发起询问,服务器还不断回复
    2. 浪费带宽, 浪费前后端资源
    3. 保证数据实时性

    长轮询:

    1. 客户端向服务器发起消息,服务端轮询, 放在另外一个地方,客户端去另外一个地方去拿数据
    2. 服务端轮询, 放在另外一个地方, 直接推给客户端


    释放客户端资源,服务器压力不可避免, 节省带宽资源
    数据实时性差

    websocket: 是一个新的协议

    1. 前后端hold住等待消息
    2. 客户端与服务器建立长连接
    • 彻底解决实时性
    • 解决了占用带宽的问题
    • 解决资源

    二 websocket的应用

    安装 pip instal gevent-websocket

    启动websocket

    from flask import Flask, request
    from geventwebsocket.websocket import WebSocket
    from gevent.pywsgi import WSGIServer
    from geventwebsocket.handler import WebSocketHandler
    
    app = Flask(__name__)
    
    
    @app.route("/ws")
    def ws():
        user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
        while 1:
            msg = user_socket.receive()
            print(msg)
            user_socket.send(msg)
    
    
    if __name__ == '__main__':
        http_serv = WSGIServer(("0.0.0.0", 9527), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    # app.run("0.0.0.0", 9527, debug=True)

    前端创建webscoket

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <script type="text/javascript">
        var ws = new WebSocket("ws://127.0.0.1:9527/ws");
        ws.onmessage = function (data) {
            console.log(data.data);
        }
    
    </script>
    </body>
    </html>

    三 WebSocket群聊

    1 带昵称的群聊

    flask代码:

    from flask import Flask, request, render_template
    from geventwebsocket.websocket import WebSocket
    from gevent.pywsgi import WSGIServer
    from geventwebsocket.handler import WebSocketHandler
    import json
    
    app = Flask(__name__)
    user_socket_dict = {}
    
    
    @app.route("/")
    def index():
        return render_template('index.html')
    
    
    @app.route("/ws/<nickname>")
    def ws(nickname):
        user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
        if user_socket:
            user_socket_dict[nickname] = user_socket
        else:
            return render_template("index.html", message="请使用websocket链接")
        while 1:
            try:
                msg = user_socket.receive()
            except:
                user_socket_dict.pop(user_socket)
            for user_nickname, socket in user_socket_dict.items():
    
                if user_socket != socket:
                    try:
                        socket.send(json.dumps({"sender": nickname, "msg": msg}))
                    except:
                        continue
    
    
    if __name__ == '__main__':
        http_serv = WSGIServer(("0.0.0.0", 9527), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()

     前端代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    
    </head>
    <body>
    <h1>欢迎使用群聊 </h1>
    <p>{{ message }}</p>
    <p>
        昵称:<input type="text" id="nickname">
        <button id="send" onclick="createSocket()">链接群聊</button>
    </p>
    消息:<input type="text" id="send_text">
    <button id="send" onclick="send_msg()">发送</button>
    <div id="text_div" style="border: 2px solid;  500px; height: 800px;">
    
    </div>
    <script type="text/javascript">
    
        var ws = null;
    
        function createSocket() {
            var nickname = document.getElementById('nickname').value;
            ws = new WebSocket("ws://127.0.0.1:9527/ws/" + nickname);
            ws.onmessage = function (data) {
                var text_div = document.getElementById("text_div");
                var obj_data = JSON.parse(data.data);
                var add_msg = "<p>" + obj_data.sender + ":" + obj_data.msg + "</p>";
                text_div.innerHTML += add_msg
            };
        }
    
    
        function send_msg() {
            var nickname = document.getElementById('nickname').value;
            var msg = document.getElementById("send_text").value;
            var text_div = document.getElementById("text_div");
            var add_msg = "<p style='text-align:right'>" + msg + ":" + nickname + "</p>";
            text_div.innerHTML += add_msg;
            ws.send(msg)
        }
    
    
    </script>
    </body>
    </html>

    2 带昵称私聊

    from flask import Flask, request, render_template
    from geventwebsocket.websocket import WebSocket
    from gevent.pywsgi import WSGIServer
    from geventwebsocket.handler import WebSocketHandler
    import json
    
    app = Flask(__name__)
    user_socket_dict = {}
    
    
    @app.route("/")
    def index():
        return render_template('index.html')
    
    
    @app.route("/ws/<nickname>")
    def ws(nickname):
        user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
        if user_socket:
            user_socket_dict[nickname] = user_socket
        else:
            return render_template("index.html", message="请使用websocket链接")
        while 1:
            msg = user_socket.receive()
            msg_dict = json.loads(msg)  # {"to_user": user_id, msg:"hello"}
            to_user = msg_dict.get("to_user")
            to_user_socket = user_socket_dict.get(to_user)
            send_str = json.dumps({"sender": nickname, "msg": msg_dict.get("msg")})
            to_user_socket.send(send_str)
    
    
    if __name__ == '__main__':
        http_serv = WSGIServer(("0.0.0.0", 9527), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()

    html:

    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    你的名字:<input type="text" id="nkname">
    <button onclick="open_chat()">登录聊天室</button>
    <p>发送至<input type="text" id="to_user"></p>
    消息<input type="text" id="message">
    <button onclick="send_msg()">发送</button>
    <div id="chat_list">
    
    </div>
    </body>
    <script type="application/javascript">
        var ws = null;
    
        function open_chat() {
            var nkname = document.getElementById('nkname').value;
            ws = new WebSocket("ws://127.0.0.1:9527/my_ws/" + nkname);
            ws.onopen = function () {
                alert('欢迎' + nkname + '登录对骂平台!!')
            };
            ws.onmessage = function (EventMessage) {
                var chat = JSON.parse(EventMessage.data);
                var p = document.createElement('p');
                p.style.cssText = 'width : 250px; text-align:left';
                p.innerText = chat.from_user + '-->' + chat.message;
                document.getElementById('chat_list').appendChild(p);
            };
            ws.onclose = function () {
                console.log('断开连接了,啥情况啊? 搞事情啊');
            };
        }
    
        function send_msg() {
            var message = document.getElementById('message').value;
            var from_user = document.getElementById('nkname').value;
            var to_user = document.getElementById('to_user').value;
            var send_str = {
                message: message,
                from_user: from_user,
                to_user: to_user
            };
            ws.send(JSON.stringify(send_str));
            var p = document.createElement('p');
            p.style.cssText = " 250px;text-align: right";
            p.innerText = send_str.message + "<-我";
            document.getElementById('chat_list').appendChild(p);
        }
    
    </script>
    </html>

    四 websocket握手

    浏览器 - 链接 - 服务器
    浏览器 - 发送 - 字符串(请求头) - Sec-Websocket-Key - 服务器

    服务器 - 获取Sec-Websocket-Key的值+magic_string - sha1 - base64加密 - 拼接一个响应头 Sec-Websocket-Accpet
    服务器 - 拼接好的响应头 - 浏览器
    浏览器 - Sec-Websocket-Accpet解密得到Sec-WebSocket-Key - 握手成功

    客户端发送一个请求

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com

    这段类似HTTP协议的握手请求中多了以下几个东西

    Upgrade: websocket
    Connection: Upgrade

    是告诉服务器我想升级为websocket协议

    Sec-WebSocket-Key是一个Base64加密的密钥
    Sec-WebSocket-Protocol是用于标识客户端想和服务端使用哪一种子协议(都是应用层的协议,比如 chat 表示采用 “聊天” 这个应用层协议)。
    Sec-WebSocket-Version是告诉服务器所使用的协议版本
    OriginOrigin可以预防在浏览器中运行的脚本,在未经 WebSocket 服务器允许的情况下,对其发送跨域的请求。浏览器脚本在使用浏览器提供的 WebSocket 接口对一个 WebSocket 服务发起连接请求时,浏览器会在请求的 Origin 中标识出发出请求的脚本所属的源,然后 WebSocket 在接受到浏览器的连接请求之后,就可以根据其中的源去选择是否接受当前的请求。

    服务端响应

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

    上面提到了这个101是状态码,websocket是使用 HTTP 协议的101状态码进行协议切换。

    Sec-WebSocket-Accept这个值表示服务器同意握手建立连接,是客户端传输过来的Sec-WebSocket-Key跟“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”拼接后,用SHA-1加密,并进行BASE-64编码得来的,Sha1(Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11)。

    客户端收到Sec-WebSocket-Accept后,将本地的Sec-WebSocket-Key进行同样的编码,然后比对。

    五 websocket加解密

    解密

    # b'x81x83xceHxb6x85xffzx85'
    
    hashstr = b'x81x83xceHxb6x85xffzx85'
    # b'x81    x83    xceHxb6x85xffzx85'
    
    # 将第二个字节也就是 x83 第9-16位 进行与127进行位运算
    payload = hashstr[1] & 127  # 拿出第二个字节hashstr[1]
    print(payload)
    if payload == 127:
        extend_payload_len = hashstr[2:10]
        mask = hashstr[10:14]
        data = hashstr[14:]
    # 当位运算结果等于127时,则第3-10个字节为数据长度
    # 第11-14字节为mask 解密所需字符串
    # 则数据为第15字节至结尾
    
    if payload == 126:
        extend_payload_len = hashstr[2:4]
        mask = hashstr[4:8]
        data = hashstr[8:]
    # 当位运算结果等于126时,则第3-4个字节为数据长度
    # 第5-8字节为mask 解密所需字符串
    # 则数据为第9字节至结尾
    
    if payload <= 125:
        extend_payload_len = None
        mask = hashstr[2:6]
        data = hashstr[6:]
    
    # 当位运算结果小于等于125时,则这个数字就是数据的长度
    # 第3-6字节为mask 解密所需字符串
    # 则数据为第7字节至结尾
    
    str_byte = bytearray()
    
    # 解密数据
    for i in range(len(data)):
        byte = data[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 < 126:
        token += struct.pack("B", length)  # 固定打包写法 三种不同打包方式
    elif length == 126:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)
    
    msg = token + msg_bytes
    
    print(msg)
  • 相关阅读:
    搜狗输入法用户体验评价
    第二阶段团队冲刺5
    第二阶段团队冲刺4
    进度总结报告十四
    第二阶段团队冲刺3
    寻找水王-课上练习
    第二阶段团队冲刺2
    大型网站处理高并发要点技术
    php 处理上百万条的数据库如何提高处理查询速度
    php一次性大量数据入库解决方法
  • 原文地址:https://www.cnblogs.com/harryblog/p/11050000.html
Copyright © 2020-2023  润新知