websocket

WebScoket简介

在以前的web应用中,双向通信机制往往借助轮询或是长轮询来实现,但是这两种方式都会或多或少的造成资源的浪费,且是非实时的。还有http长连接,但是本质上还是Request与Response,只是减少握手连接次数,虽然减少了部分开销,但仍然会造成资源的浪费、实时性不强等问题。

WebSocket作为一种解决web应用双向通信的协议由HTML5规范引出(RFC6455传送门),是一种建立在TCP协议基础上的全双工通信的协议IE浏览器用的不是websocket,是轮询

WebSocket 是什么?

WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

为什么需要 WebSocket ?

了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

WebSocket 如何工作?

Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。

基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。

HTTP 和 WebSocket 有什么关系?

Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是 HTTP 协议上的一种补充

Html 和 HTTP 有什么关系?

Html 是超文本标记语言,是一种用于创建网页的标准标记语言。它是一种技术标准。Html5 是它的最新版本

Http 是一种网络通信协议。其本身和 Html 没有直接关系。

WebSocket 代理

如果把 WebSocket 的通信看成是电话连接,Nginx 的角色则像是电话接线员,负责将发起电话连接的电话转接到指定的客服。

Nginx 从 1.3 版开始正式支持 WebSocket 代理。如果你的 web 应用使用了代理服务器 Nginx,那么你还需要为 Nginx 做一些配置,使得它开启 WebSocket 代理功能。

以下为参考配置:

server {
  # this section is specific to the WebSockets proxying
  location /socket.io {
    proxy_pass http://app_server_wsgiapp/socket.io;
    proxy_redirect off;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 600;
  }
}

与Socket、HTTP的关系

很多人刚接触WebSocket肯定会与Socket混淆,这里放出OSI模型

理解socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,socket是对TCP/IP协议的封装本身是是一组接口(API)。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。

TCP是传输层的协议,WebScoket和HTTP都是基于TCP协议的高层(应用层)协议,所以从本质上讲,WebSocket和HTTP是处于同一层的两种不同的协议。但是WebSocket使用了HTTP完成了握手连接,根据RFC6455文档中1.5节设计哲♂学中描述,是为了简单和兼容性考虑。具体握手操作我们会在后面提到。

所以总的来说,WebSocket与Socket由于层级不同,关系也仅仅是在某些环境中WebSocket可能通过Socket来使用TCP协议和名字比较像。和HTTP是同一层面的不同协议(最大的区别WebSocket是持久化协议而HTTP不是)。

这里取网上流传度很高的例子介绍

轮询:
客户端(发请求,建立链接):啦啦啦,有没有新信息(Request)
服务端:没有(Response)
客户端(发请求,建立链接):啦啦啦,有没有新信息(Request)
服务端:没有。。(Response)
客户端(发请求,建立链接):啦啦啦,有没有新信息(Request)
服务端:你好烦啊,没有啊。。(Response)
客户端(发请求,建立链接):啦啦啦,有没有新消息(Request)
服务端:好啦好啦,有啦给你。(Response)
客户端(发请求,建立链接):啦啦啦,有没有新消息(Request)
服务端:。。。。。没。。。。没。。。没有(Response)

长轮询:
客户端(发请求,建立链接):啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
等等等。。。。。
服务端:额。。 等待到有消息的时候。。来 给你(Response)
客户端(发请求,建立链接):啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)

WebSocket:
客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
客户端:麻烦你有信息的时候推送给我噢。。
服务端:ok,有的时候会告诉你的。
服务端:balabalabalabala
客户端:balabalabalabala
服务端:哈哈哈哈哈啊哈哈哈哈
服务端:笑死我了哈哈哈哈哈哈哈

从上面的例子可以看出,不管是轮询还是长轮询,本质都是不断地发送HTTP请求,然后由服务端处理返回结果,并不是真正意义上的双向通信。而且带来的后果是大量的资源被浪费(HTTP请求),服务端需要快速的处理请求,还要考虑并发等问题。而WebSocket解决了这些问题,通过握手操作后就建立了持久连接,之后客户端和服务端在连接断开之前都可以发送消息,实现真正的全双工通信。

WebScoket协议

这里主要提一下协议中比较重要的握手和发送数据

一、握手

之前有说到,WebSocket的握手是用HTTP请求来完成的,这里我们来看一下RFC6455文档中一个客户端握手的栗子

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

可以发现,这和一个一般的HTTP请求头没啥区别,需要注意的是(这里讲重点,具体还请看协议文档):

  • 根据协议规范,握手必须是一个HTTP请求,请求的方法必须是GET,HTTP版本不可以低于1.1。
  • 请求头必须包含Upgrade属性名,其值必须包含"websocket"。
  • 请求头必须包含Connection属性名,其值必须包含"Upgrade"。
  • 请求头必须包含Sec-WebSocket-Key属性名,其值是16字节的随机数的被base64编码后的值
  • 如果请求来自浏览器必须包含Origin属性名
  • 请求头必须包含Sec-WebSocket-Version属性名,其值必须是13

如果请求不符合规范,服务端会返回400 bad request。如果服务端选择接受连接,则会返回比如:

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

首先不同于普通的HTTP请求这里返回101,然后Upgrade和Connection同上都是规定好的,Sec-WebSocket-Accept是由请求头的Sec-WebSocket-Key加上字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11之后再进行SHA1加密和BASE64编码得到的值。返回的状态码为101,表示同意客户端协议转换请求,并将它转换为websocket协议。

在握手成功之后,WebSocket连接建立,双向通信便可以开始了。

二、数据帧类型

  • 0x0 表示一个继续帧
  • 0x1 表示一个文本帧
  • 0x2 表示一个二进制帧
  • 0x3-7 为以后的非控制帧
  • 0x8 表示一个连接关闭帧
  • 0x9 表示一个ping
  • 0xA 表示一个pong
  • 0xB-F 为以后的控制帧

大部分都十分明了,这里来说说Ping,Pong帧:WebSocket用Ping,Pong帧来维持心跳,当接收到Ping帧,终端必须发送一个Pong帧响应,除非它已经接收到一个关闭帧,它应该尽快返回Pong帧作为响应。Pong帧必须包含与被响应Ping帧的应用程序数据完全相同的数据。一个Pong帧可能被主动发送,但一般不必须返回响应,也可以做特殊处理。

三、发送数据

在WebSocket协议,数据使用帧来传输。一个基本的协议帧如下

WebSocket 客户端

在客户端,没有必要为 WebSockets 使用 JavaScript 库。实现 WebSockets 的 Web 浏览器将通过 WebSockets 对象公开所有必需的客户端功能(主要指支持 Html5 的浏览器)。

客户端 API

以下 API 用于创建 WebSocket 对象。

var Socket = new WebSocket(url, [protocol] );

以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。

WebSocket 属性

以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:

属性描述
Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

WebSocket 事件

以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

事件事件处理程序描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

WebSocket 方法

以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

方法描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

示例

// 初始化一个 WebSocket 对象
var ws = new WebSocket("ws://localhost:9998/echo");

// 建立 web socket 连接成功触发事件
ws.onopen = function () {
  // 使用 send() 方法发送数据
  ws.send("发送数据");
  alert("数据发送中...");
};

// 接收服务端数据时触发事件
ws.onmessage = function (evt) {
  var received_msg = evt.data;
  alert("数据已接收...");
};

// 断开 web socket 连接成功触发事件
ws.onclose = function () {
  alert("连接已关闭...");
};

WebSocket 服务端

WebSocket 在服务端的实现非常丰富。Node.js、Java、C++、Python 等多种语言都有自己的解决方案。

以下,介绍我在学习 WebSocket 过程中接触过的 WebSocket 服务端解决方案。

Node.js

常用的 Node 实现有以下三种。

简单的websocket聊天

py

from flask import Flask, request
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
from geventwebsocket.websocket import WebSocket  # 做语法提示用
import json

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(json.dumps({"id": msg}))


if __name__ == '__main__':
    http_serv = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler)
    http_serv.serve_forever()

html

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

    }
</script>
</body>
</html>

群聊

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>
    发送消息:<input type="text" id="msg">
    <button onclick="send_msg()">发送</button>
</p>
<div id="chat" style="500px;height: 500px;">

</div>
<script>
    var ws = new WebSocket("ws://192.168.19.25:5000/ws");  //自己的ip
    ws.onmessage = function (data) {  //onmessage接收到数据时执行的方法,类似于success
        // var msg = JSON.parse(data.data);
        // console.log(msg)
        console.log(data.data);
        var ptag=document.createElement("p");  //生成p标签
        ptag.innerText=data.data;
        document.getElementById("chat").appendChild(ptag);
    };

    function send_msg() {
        var msg=document.getElementById("msg").value;  //取到msg的值
        ws.send(msg);
    }
</script>
</body>
</html>

py

from flask import Flask, request,render_template
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
from geventwebsocket.websocket import WebSocket
import json

app = Flask(__name__)

user_socket_list=[]


@app.route("/ws")
def ws():
    user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
    user_socket_list.append(user_socket)
    print(len(user_socket_list),user_socket_list)
    while 1:
        try:
            msg = user_socket.receive()
            for usocket in user_socket_list:
                if usocket!=user_socket:
                    usocket.send(msg)
                    print(msg)
        except:
            user_socket_list.remove(user_socket)


@app.route("/chat")
def chat():
    return render_template("ws2.html")


if __name__ == '__main__':
    http_serv = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler)
    http_serv.serve_forever()

单聊

py

from flask import Flask, request, render_template
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
from geventwebsocket.websocket import WebSocket  # 做语法提示用
import json

app = Flask(__name__)

user_socket_dict = {}  # 一般定义字典,方便数据交互


@app.route("/ws/<username>")  # 动态传参
def ws(username):
    user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
    user_socket_dict[username] = user_socket
    print(len(user_socket_dict), user_socket_dict)
    while 1:
        try:
            msg = json.loads(user_socket.receive())
            to_user = msg.get("to_user")  # 发给谁
            content = msg.get("msg")  # 发送的内容
            usocket = user_socket_dict.get(to_user)
            recv_msg = {"from_user": username, "msg": content}  # 接收数据的格式
            usocket.send(json.dumps(recv_msg))

        except:
            pass


@app.route("/chat")
def chat():
    return render_template("ws3.html")


if __name__ == '__main__':
    http_serv = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler)
    http_serv.serve_forever()

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" id="username">
<button onclick="open_ws()">链接服务器</button>
<p>给:<input type="text" id="to_user"></p>
<p>
    发送消息:<input type="text" id="msg">
    <button onclick="send_msg()">发送</button>
</p>
<div id="chat" style="500px;height: 500px;">

</div>
<script type="text/javascript">
    var ws = null;

    function open_ws() {
        var username = document.getElementById("username").value; //通过绑定事件来添加动态参数
        ws = new WebSocket("ws://192.168.19.25:5000/ws/" + username);  //自己的ip
        ws.onmessage = function (data) {
            console.log(data.data);
            var msg = JSON.parse(data.data);
            var ptag = document.createElement("p");
            ptag.innerText = msg.from_user + ":" + msg.msg;
            document.getElementById("chat").appendChild(ptag);
        };

    }


    function send_msg() {
        var msg = document.getElementById("msg").value;
        var to_user = document.getElementById("to_user").value;
        var send_obj = {to_user: to_user, msg: msg};
        ws.send(JSON.stringify(send_obj));
    }
</script>
</body>
</html>

位运算

位运算符比一般的算术运算符速度要快,而且可以实现一些算术运算符不能实现的功能。如果要开发高效率程序,位运算符是必不可少的。位运算符用来对二进制位进行操作,包括:按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、按位左移(<<)、按位右移(>>)。下面就给大家介绍位运算符的详细用法。

指定 A = 60(0011 1100); B = 13 (0000 1101)

  1. 按位与(&)

    对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1,如下图:

    (A & B) 结果为 12, 二进制为 0000 1100

  2.  按位或(|)

    比较两个数,然后返回一个新的数,这个数的每一位设置1的条件是两个输入数的同一位都不为0(即任意一个为1,或都为1),如下图:

    (A | B) 结果为 61, 二进制为 0011 1101

  3. 按位异或(^)

    比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0,如下图:

    (A ^ B) 结果为 49, 二进制为 0011 0001

  4. 按位取反(~)

    对一个操作数的每一位都取反,如下图:

    (~A ) 结果为 -61, 二进制为 1100 0011

  5. 按位左移(<<)

    将操作数的所有位向左移动指定的位数。

    下图展示了11111111 << 1(11111111 左移一位)的结果。蓝色数字表示被移动位,灰色表示被丢弃位,空位用橙色的0填充。

    (A << 2)结果为 240, 二进制为 1111 0000

  6. 按位右移(<<)

    将操作数的所有位向又移动指定的位数。

    下图展示了11111111 >> 1(11111111 右移一位)的结果。蓝色数字表示被移动位,灰色表示被丢弃位,空位用橙色的0填充。

    A >> 2 结果为 15, 二进制为 0000 1111

手写一个websocket(握手)

握手原理:请求头中有个sec-websocket-key,和我们的magic_string 做个拼接,然后进行sha1加密,后在进行base64加密,返回一个响应头
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', 9527))
sock.listen(5)
# 获取客户端socket对象
conn, address = sock.accept()
# 获取客户端的【握手】信息
data = conn.recv(1024)
print(data)

"""
b'GET / HTTP/1.1

Host: 127.0.0.1:9527

Connection: Upgrade

Pragma: no-cache

Cache-Control: no-cache

Upgrade: websocket

Origin: http://localhost:63342

Sec-WebSocket-Version: 13

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

Cookie: sessionid=jx8dbvvo9zu6s35ynjjl1z2itf7axplt; csrftoken=cgDFp4tfQXPIei7pyKAGRtmMWtPSsTwu7tSDwyDVEPvWCqwhKCciSPjOksKTkex8

Sec-WebSocket-Key: zVe5rLp79bRrqOxQP5Gbtg==
  # 请求头中必须有Sec-WebSocket-Key才能建立连接
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

'
"""

magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'  # 魔法字符串,世界通用,不会变


#
def get_headers(data):  # 为了拿到Sec-WebSocket-Key
    header_dict = {}
    header_str = data.decode("utf8")
    for i in header_str.split("
"):
        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('

', 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
#
#
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://127.0.0.1:9527

"

value = headers['Sec-WebSocket-Key'] + magic_string
print(value)
# 先将value进行sha1加密,后在进行base64加密
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())  # 握手信息

response_str = response_tpl % (ac.decode('utf-8'))
# 响应【握手】信息
conn.send(response_str.encode("utf8"))
# while True: 解释握手原理时使用
#     msg = conn.recv(8096)
#     print(msg)

import websocket_encrypt  # websocket_encrypt是加密的py文件
while True:  # 握手,加密,解密时使用
    msg = conn.recv(8096)
    st=websocket_encrypt.encrypt()
    print(st)
    conn.send(st)

# 握手原理:请求头中有个sec-websocket-key,和我们的magic_string 做个拼接,然后进行sha1加密,后在进行base64加密,返回一个响应头

解密:

# 解密原理:
# 和127做位运算的三种情况:
#     payload == 127
#     payload == 126
#     payload <= 125
# b'x81x83xceHxb6x85xffzx85'

hashstr = b'x81x83xceHxb6x85xffzx85'
hashstr = b"x81x98x8f?x9d5fxb8x0cxdd9xbaxxa7x03xda-xbajx83'xd2x15xbb{xa0
xdb'xbe"
hashstr = b'x81xfex01)xfcx0exb4Ux19xbfx05xb1Dx83Qxc9Txe7x1fxcdx13xb28xb3`x87Pxeeexeb<xccx19x9e9xb6|x8cRxe5Hxeax0cxd8x19x92x1cxb3Kxbf[xe9pxe8(xdcx15xb0-xb0tx97Sxd4Ixed4xd7x1ax98x1bxb3dxa1]xccwxebx1axf1x13xb28xb3x7fx91Qxc5Bxeb
xe2x15xa8x1cxb6|x8c\xdehxe9#xc0x18xb6>xbcdxb8SxeeCxe1x08xd9x14x83=xbduxbcQxd0Yxebx0cxcdx15x93&xb6|x8c\xe5txe9x18xc4x1ax92=xbcDxb1Qxd1nxe1x08xd9x19xb04xb3axabRxc2\xe9-xe8x18xb65xb6|x8cQxdaSxeax0fxf0x14xbe7xb2HxaeSxc5Hxe1x08xd9x15x961xbc{x9fSxeesxed4xd7x1ax99x14xb1Dx93SxfeExea
xdex18xb7x05xbd|xbd[xe9pxe8#xf5x1axaf<xb2ux83Pxecwxeb>xe6x19xb3x16xb6|x8cQxd8kxe7,xe6x14xa1x0cxbdmx95Qxeflxe1x08xd9x14xabx0bxbd`x8eQxf8lxeax0exc4x18xb4x19xb6|x8cQxf8hxebx19xc5x18xb4%xba@x94Pxe8ixe7-xdex18xb7?xb3`x87[xe9c'


# b'x81    x83    xceHxb6x85xffzx85'

# 将第二个字节也就是 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)
    # 所有的字节一个一个的做位运算,然后在和mask秘钥进行解密,将每个字节添加到数组,将数组打印出来字

print(str_byte.decode("utf8"))

# 解密原理:
# 和127做位运算的三种情况:
#     payload == 127
#     payload == 126
#     payload <= 125

加密:

加密原理:b"x81"打包(struct.pack)
import struct
def encrypt():  # 加密
    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

    return msg

# 加密原理:b"x81"打包(struct.pack)

123

原文地址:https://www.cnblogs.com/daofaziran/p/10007908.html