最近做项目常常涉及到后台一些复杂的模型算法,前端常常需要等待比较久,此时后台需要主动将当前模型运行进度返回给前端,提高用户体验。
或者是一些类仿真模型,在仿真过程中需要后台主动将仿真结果返回前端。
此时都要用到 web socket , 提升用户体验。
个人理解websocket 是应用层协议。 我们应该在tcp协议基础上 对数据进行封装和解析。下面直接贴上websocket解析和封装的代码。
# 从websocket数据包中 获取websocket消息头
def get_headers(data):
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(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()
print(header_dict)
return header_dict
知识兔# 从websocket 获取数据体
# conn是python socket对象 要接收数据的客户端文件描述符
def recv_msg(conn):
info = conn.recv[8096]
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')
return body
# 向websocket 写入
# conn是python socket对象 要写入数据的客户端文件描述符
def send_msg(conn, msg):
msg_bytes = bytes(msg, encoding='utf-8')
token = b"\x81"
length = len(msg_bytes)
import struct
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
知识兔我们采用 select 模型作为 tcp服务端模型响应多个客户端
message_queues = {}
client_socket_fd_map = {}
def start_socket_select_server():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 8002))
sock.listen(5)
print("WebSocket 服务器启动成功 监听IP", ('127.0.0.1', 8002))
sock.setblocking(False)
inputs = [sock, ]
outputs = []
while True:
readable, writeable, exceptional = select.select(inputs, outputs, inputs)
# print('select finish, inputs size:%d, outputs size:%d' % (len(inputs), len(outputs)))
for s in readable:
if s is sock:
conn, client_addr = s.accept()
print("new connection from", client_addr)
conn.setblocking(False)
inputs.append(conn)
message_queues[conn] = queue.Queue()
else:
if s not in outputs:
# 第一次 表示 websocket的握手
data = s.recv(1024)
if data:
print('received [%s] from %s' % (data, s.getpeername()[0]))
# message_queues[s].put(data)
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"
sha1 = hashlib.sha1()
magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
value = headers['Sec-WebSocket-Key'] + magic_string
sha1.update(value.encode('utf-8'))
ac = base64.b64encode(sha1.digest())
response_str = response_tpl % (
ac.decode('utf-8'), headers['Host'], headers['url'])
s.send(bytes(response_str, encoding='utf-8'))
# 这里将文件描述符返回给浏览器 浏览器可以在接下来的http请求中带上这个参数 服务端就可以向这个文件描述符中写入信息返回给指定浏览器
fileno_dict_str = '{"type":1, "body":%s}' % s.fileno()
message_queues[s].put(fileno_dict_str)
if s not in outputs:
outputs.append(s)
client_socket_fd_map[s.fileno()] = s
else:
# 表示客户端已经断开
print("~~~~~~~~~~~client [%s] closed" % s)
if s in outputs:
outputs.remove(s)
del message_queues[s]
del client_socket_fd_map[s.fileno()]
inputs.remove(s)
s.close()
else:
# websocket 通信
data = s.recv(8096)
if data:
pass
else:
# 表示客户端已经断开
print("-------------client [%s] closed" % s)
if s in outputs:
outputs.remove(s)
del message_queues[s]
del client_socket_fd_map[s.fileno()]
inputs.remove(s)
s.close()
for s in writeable:
try:
next_msg = message_queues[s].get_nowait()
except queue.Empty:
pass
else:
send_msg(s, next_msg)
知识兔其中message_queue 是要返回给浏览器的消息队列
client_socket_fd_map 是一个将文件描述符和客户端socket对象关联起来的字典。
下面开始测试。
前端测试代码如下:
<script>
// 初始化一个 WebSocket 对象
console.log('begin');
var ws = new WebSocket('ws://localhost:8002');
// 接收服务端数据时触发事件
ws.onmessage = function(evt) {
var received_msg = evt.data;
console.log('数据已接收...' + received_msg);
};
</script>
知识兔运行结果如下:
之后服务端通过 send_msg函数以及文件描述符id向客户端发送信息