zoukankan      html  css  js  c++  java
  • websocket

    WebSocket、HTTP 与 TCP

    HTTP、WebSocket 等应用层协议,都是基于 TCP 协议来传输数据的。我们可以把这些高级协议理解成对 TCP 的封装。
    既然大家都使用 TCP 协议,那么大家的连接和断开,都要遵循 TCP 协议中的三次握手和四次挥手 ,只是在连接之后发送的内容不同,或者是断开的时间不同.
    我们知道http是基于tcp实现的,浏览器可以看做是客户端socket,这个socket在开发的时候遵循http规范,数据在发送并接收成功的时候就断开socket了,那么如果我们的socket一直连接不断开,并且发送数据的时候都把数据进行加密,这样是不是也是可以的呢?这种想法就可以引出websocket了,值得一提的是,websocket的兼容性不是所有版本的浏览器都支持(取决于是否能够new WebSocket), 但是随着技术的发展,浏览器对websocket的支持肯定会越来越好。
    对于 WebSocket 来说,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。
    Websocket和socket不同,Websocket工作在应用层,而socket是基于门面模式的一种封装,可以让程序员简便地写网络通信程序

    flask实现websocket

    安装gevent-websocket模块

    from flask import Flask,render_template,request
    from geventwebsocket.handler import WebSocketHandler
    from gevent.pywsgi import WSGIServer
    import json
    
    app = Flask(__name__)
    
    USERS = {
        '1':{'name':'jack','count':0},
        '2':{'name':'tuple','count':0},
        '3':{'name':'lily','count':100},
    }
    
    @app.route('/index')
    def index():
        return render_template('index.html',users=USERS)
    
    # http://127.0.0.1:5000/message  既可以接受http,也可以接受websocket
    WEBSOCKET_LIST = []
    @app.route('/message')
    def message():
        ws = request.environ.get('wsgi.websocket')
        if not ws:
            return '您使用的是Http协议'
        WEBSOCKET_LIST.append(ws)
        while True:
            # 当关闭webscoekt的时候cid 得到None
            cid = ws.receive()
            if not cid:
                WEBSOCKET_LIST.remove(ws)
                ws.close()
                break
            old_count = USERS[cid]['count']
            new_count = old_count + 1
            USERS[cid]['count'] = new_count
            for client in WEBSOCKET_LIST:
                client.send(json.dumps({'cid':cid,'count':new_count}))
        return ''
    
    if __name__ == '__main__':
        # 当请求是websocket的时候,那么就用WebSocketHandler进行处理
        http_server = WSGIServer(('0.0.0.0', 5000), app, handler_class=WebSocketHandler)
        http_server.serve_forever()
    

    index.html

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <h1>投票系统</h1>
        <ul>
            {% for k,v in users.items() %}
                <li onclick="vote({{k}})" id="id_{{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 ws = new WebSocket('ws://127.0.0.1:5000/message');
            // 事件监听绑定回调函数,一旦有消息发送过来就会调用函数
            ws.onmessage = function (event) {
                /* 服务器端向客户端发送数据时,自动执行 */
                // {'cid':cid,'count':new}
                var response = JSON.parse(event.data);
                $('#id_'+response.cid).find('span').text(response.count);
    
            };
    
            function vote(cid) {
                ws.send(cid)
            }
        </script>
    </body>
    </html>
    

    webscoket 原理

    websocket的握手需要借助http, new WebSocket的时候,服务端接收到的的请求头信息有有三个比较特殊:

    Sec-WebSocket-Key:FUH2Nige4Npq/InFS0OoJQ==
    Upgrade:websocket
    Connection: Upgrade
    

    表示这次请求是用于升级http为websocket,并把一个随机字符串发送过去,此时服务端应该拿到这个字符串进行加密然后发送过去,浏览器对这个字符串也加密,比较加密后的字符串和服务端发送过来的字符串是否一致,一致就表名握手成功,此时response header为

    Connection:Upgrade
    Sec-WebSocket-Accept:+lZCKceYfJsmNj6q0GYPa9r9LXE=
    Upgrade:websocket
    

    状态码为:101 Switching Protocols,表示协议切换成功,此时,后面的通信就和http没有半毛钱关系了。

    自定义实现websocket服务端

    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"
        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)
    
    
    import base64
    import hashlib
    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)
    
    while 1:
        # 1. 等待用户连接
        conn, address = sock.accept()
    
        # 2. 接收验证消息
        msg = conn.recv(8096)
        msg_dict = get_headers(msg)
    
        # 3. 对数据加密, 其中魔法字符串'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 是固定的值
        value = msg_dict['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
    
        # 4. 将加密后的结果返回给浏览器,发送的时候是用http通信的,回消息的时候也要封装成http格式
        response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
              "Upgrade:websocket
    " 
              "Connection: Upgrade
    " 
              "Sec-WebSocket-Accept: %s
    " 
              "WebSocket-Location: ws://127.0.0.1:8002
    
    "
        response = response_tpl %(ac.decode('utf-8'),)
        conn.send(response.encode('utf-8'))
    
        # 5. 接受浏览器发送过来的加密数据,并进行解密,发送数据也需要进行加密,加密和解密规则可以参考官方
        while True:
    
            info = conn.recv(8096)
            # 10111111 去掉第一位,和 01111111 相与就行
            payload_len = info[1] & 127
            if payload_len == 127:
                # 如果payload_len是127,那么需要往后面取8个字节作为头信息,头部总共10个字节
                extend_payload_len = info[2:10]
                mask = info[10:14]
                decoded = info[14:]
            elif payload_len == 126:
                # 如果payload_len是126,那么需要往后面取2个字节作为头信息,头部总共4个字节
                extend_payload_len = info[2:4]
                mask = info[4:8]
                decoded = info[8:]
            else:
                # 如果payload_len小于126,那么头部总共2个字节
                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('浏览器发送过来的数据',body)
            #
            # import  time
            # time.sleep(2)
            send_msg(conn,body.encode('utf8'))
    

  • 相关阅读:
    测试候选区
    This is my new post
    发布到首页候选区
    nb
    []
    隐藏列(gridview遍历)
    交叉表、行列转换和交叉查询经典
    数据库设计中的14个技巧
    jQuery操作表格,table tr td,单元格
    不愿将多种编程语言杂糅在一起?可能你还没意识到而已
  • 原文地址:https://www.cnblogs.com/longyunfeigu/p/9574067.html
Copyright © 2011-2022 走看看