Websocket - Websocket原理(握手、解密、加密)、基于Python实现简单示例

时间:2022-06-09 19:45:26

一、Websocket原理(握手、解密、加密)

  WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。

  本文将使用Python编写Socket服务端,一步一步分析请求过程!!!

1、启动服务端

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', 9527))
sock.listen(5)
# 获取客户端socket对象,并等待用户连接
conn, address = sock.accept()
......
......
......

  启动Socket服务器后,等待用户【连接】,然后进行收发数据。

2、客户端连接

<script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:9527/xxoo");
......
</script>

  当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!

3、建立连接【握手】

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', 9527))
sock.listen(5)
# 获取客户端socket对象,并等待用户连接
conn, address = sock.accept()
# 获取客户端的【握手】信息,data即握手信息
data = conn.recv(1024)
......
......
conn.send('响应【握手】信息')

  请求和响应的【握手】信息需要遵循规则:

    - 从请求【握手】信息中提取 Sec-WebSocket-Key;

    - 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密;

    - 将加密结果响应给客户端;

  注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

  请求【握手】信息为:

b'GET /xxoo HTTP/1.1\r\n
Host: 127.0.0.1:9527\r\n
Connection: Upgrade\r\n
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36\r\n
Upgrade: websocket\r\n
Origin: http://localhost:63342\r\n
Sec-WebSocket-Version: 13\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: session=ba01367c-59b9-41d4-81ba-30b70db282c6\r\n
Sec-WebSocket-Key: jLSLU57WxRJACRQxlN47Tw==\r\n
Sec-WebSocket-Extensions: permessage-deflate;
......'

  提取Sec-WebSocket-Key值并加密:

import socket
import base64
import hashlib def get_headers(data):
"""
将请求头格式化成字典
:param data: 请求头
:return: 请求头信息字典
"""
header_dict = {}
header_str = data.decode("utf8")
for i in header_str.split("\r\n"):
if str(i).startswith("Sec-WebSocket-Key"):
header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip() return header_dict 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)
headers = get_headers(data) # 提取请求头信息
# 对请求头中的Sec-WebSocket-Key进行加密
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:9527\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'))
# 响应【握手】信息
conn.send(response_str.encode("utf-8"))
......
......

  至此,客户端与服务端完成握手过程。

4、客户端与服务端收发数据

  客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。客户端的JavaScript类库已经封装【封包】和【解包】过程,但Socket服务端需要手动实现。

  第一步:获取客户端发送的数据【解包】

# b'\x81\x86#\xa47\x93\xc7\x19\x97v\x86\x19'     你好

hashstr = b'\x81\x83\xceH\xb6\x85\xffz\x85'
# b'\x81 \x86# \xa47\x93\xc7\x19 \x97v\x86\x19' # 将第二个字节 \x86# 也就是第9-16位 与 127 进行位运算
payload_len = hashstr[1] & 127 if payload_len == 127:
extend_payload_len = hashstr[2:10]
mask = hashstr[10:14]
decoded = hashstr[14:]
# 当位运算结果等于127时,则第3-10个字节为数据长度
# 第11-14字节为mask ,即解密所需字符串
# 则数据为第15字节至结尾 if payload_len == 126:
extend_payload_len = hashstr[2:4]
mask = hashstr[4:8]
decoded = hashstr[8:]
# 当位运算结果等于126时,则第3-4个字节为数据长度
# 第5-8字节为mask ,即解密所需字符串
# 则数据为第9字节至结尾 if payload_len <= 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) print(str_byte.decode("utf8"))

  解包详细过程:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+

  第二步:向客户端发送数据【封包】

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 == 126:
token += struct.pack("!BH", 126, length)
else:
token += struct.pack("!BQ", 127, length) msg = token + msg_bytes
conn.send(msg)
return True

二、基于Python实现简单示例

1、基于Python socket实现的WebSocket服务端

# !/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import base64
import hashlib def get_headers(data):
"""
将请求头格式化成字典
:param data: 请求头
:return: 请求头信息字典
"""
header_dict = {}
header_str = data.decode("utf8")
for i in header_str.split("\r\n"):
if str(i).startswith("Sec-WebSocket-Key"):
header_dict["Sec-WebSocket-Key"] = i.split(":")[1].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 == 126:
token += struct.pack("!BH", 126, length)
else:
token += struct.pack("!BQ", 127, length) msg = token + msg_bytes
conn.send(msg)
return True def run():
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) conn, address = sock.accept()
data = conn.recv(1024)
headers = get_headers(data)
# 对请求头中的Sec-WebSocket-Key进行加密
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:9527\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'))
# 响应【握手】信息
conn.send(response_str.encode("utf-8")) while True:
try:
info = conn.recv(8096)
except Exception as e:
info = None
if not info:
break
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')
send_msg(conn, body.encode('utf-8')) sock.close() if __name__ == '__main__':
run()

2、利用JavaScript类库实现客户端

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div>
<input type="text" id="txt"/>
<input type="button" id="btn" value="提交" onclick="sendMsg();"/>
<input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
</div>
<div id="content"></div> <script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:9527/chatsocket"); socket.onopen = function () {
/* 与服务器端连接成功后,自动执行 */ var newTag = document.createElement('div');
newTag.innerHTML = "【连接成功】";
document.getElementById('content').appendChild(newTag);
}; socket.onmessage = function (event) {
/* 服务器端向客户端发送数据时,自动执行 */
var response = event.data;
var newTag = document.createElement('div');
newTag.innerHTML = response;
document.getElementById('content').appendChild(newTag);
}; socket.onclose = function (event) {
/* 服务器端主动断开连接时,自动执行 */
var newTag = document.createElement('div');
newTag.innerHTML = "【关闭连接】";
document.getElementById('content').appendChild(newTag);
}; function sendMsg() {
var txt = document.getElementById('txt');
socket.send(txt.value);
txt.value = "";
}
function closeConn() {
socket.close();
var newTag = document.createElement('div');
newTag.innerHTML = "【关闭连接】";
document.getElementById('content').appendChild(newTag);
} </script>
</body>
</html>

更多websocket详解参见:http://www.runoob.com/html/html5-websocket.html

websocket原理参考博客:https://www.cnblogs.com/fuqiang88/p/5956363.html