websocket

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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(\'\\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)


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\\r\\n" \\
          "Upgrade:websocket\\r\\n" \\
          "Connection: Upgrade\\r\\n" \\
          "Sec-WebSocket-Accept: %s\\r\\n" \\
          "WebSocket-Location: ws://127.0.0.1:8002\\r\\n\\r\\n"
    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\'))

以上是关于websocket的主要内容,如果未能解决你的问题,请参考以下文章

“未捕获的类型错误:无法在 Websocket Angular JS 上读取未定义的属性‘延迟’”

websocket弹簧启动设置

WebSocket - 关闭握手 Gorilla

WebSocket 在 1000 条消息后关闭

使用 FFmpeg 通过管道输出视频片段

低延迟 websocket html 5 游戏的数据包大小