This time I will bring you an analysis of the use of WebSocket communication. What are the precautions for the analysis of WebSocket communication use? The following is a practical case, let's take a look.
What is WebSocket?
WebSocket is a standard protocol for bidirectional data transmission between client and server. But it has nothing to do with HTTP. It is an independent implementation based on TCP.
In the past, if the client wanted to know the processing progress of the server, it had to constantly use Ajax to poll, and let the browser send a request to the server every few seconds, which put great pressure on the server. Another kind of polling is to use long poll, which is similar to making a phone call. It will not hang up until a message is received. In other words, after the client initiates a connection, if there is no message, the Response will not be returned to the client. , the connection phase is always blocked.
And WebSocket solves these problems of HTTP. After the server completes the protocol upgrade (HTTP -> WebSocket), the server can actively push information to the client, solving the synchronization delay problem caused by polling. Since WebSocket only requires one HTTP handshake, the server can keep communicating with the client until the connection is closed. This eliminates the need for the server to repeatedly parse the HTTP protocol and reduces resource overhead.
With the advancement of new standards, WebSocket has become more mature, and various mainstream browsers have better support for WebSocket (not compatible with lower versions of IE, IE below 10 ), you can take a look when you have time.
When using WebSocket, the front-end use is relatively standardized. js supports the ws protocol. It feels similar to a lightly encapsulated Socket protocol, but you need to maintain the Socket yourself before. Connections can now be made in a more standard way.
Let’s talk about the communication process of WebSocket in detail based on the above picture.
Establish connection
Client request message Header
Client request message:
GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: example.com Origin: http://example.com Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
Differences from traditional HTTP messages:
Upgrade: websocket Connection: Upgrade
These two lines indicate that the WebSocket protocol is initiated.
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
Sec-WebSocket-Key is randomly generated by the browser and provides basic protection against malicious or unintentional connections.
Sec-WebSocket-Version indicates the version of WebSocket. Initially, there were too many WebSocket protocols, and different manufacturers had their own protocol versions, but now it has been settled. If the server does not support this version, a Sec-WebSocket-Versionheader needs to be returned, which contains the version number supported by the server.
Create a WebSocket object:
var ws = new websocket("ws://127.0.0.1:8001");
ws means using the WebSocket protocol, followed by the address and port
Complete client code:
<script type="text/javascript"> var ws; var box = document.getElementById('box'); function startWS() { ws = new WebSocket('ws://127.0.0.1:8001'); ws.onopen = function (msg) { console.log('WebSocket opened!'); }; ws.onmessage = function (message) { console.log('receive message: ' + message.data); box.insertAdjacentHTML('beforeend', '<p>' + message.data + '</p>'); }; ws.onerror = function (error) { console.log('Error: ' + error.name + error.number); }; ws.onclose = function () { console.log('WebSocket closed!'); }; } function sendMessage() { console.log('Sending a message...'); var text = document.getElementById('text'); ws.send(text.value); } window.onbeforeunload = function () { ws.onclose = function () {}; // 首先关闭 WebSocket ws.close() }; </script>
Server response message Header
First let’s take a look at the server response message:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
Let’s explain it line by line
First, 101 Status code indicates that the server has understood the client's request and will notify the client through the Upgrade message header to use a different protocol to complete the request;
Then, Sec-WebSocket-Accept is the Sec-WebSocket-Key that has been confirmed by the server and encrypted;
Finally, Sec-WebSocket-Protocol is is the protocol that represents the end use.
Calculation method of Sec-WebSocket-Accept:
Splice Sec-WebSocket-Key with 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ;
Calculate the digest through SHA1 and convert it into a base64 string.
注意:Sec-WebSocket-Key/Sec-WebSocket-Accept 的换算,只能带来基本的保障,但连接是否安全、数据是否安全、客户端 / 服务端是否合法的 ws 客户端、ws 服务端,其实并没有实际性的保证。
创建主线程,用于实现接受 WebSocket 建立请求:
def create_socket(): # 启动 Socket 并监听连接 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.bind(('127.0.0.1', 8001)) # 操作系统会在服务器 Socket 被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.listen(5) except Exception as e: logging.error(e) return else: logging.info('Server running...') # 等待访问 while True: conn, addr = sock.accept() # 此时会进入 waiting 状态 data = str(conn.recv(1024)) logging.debug(data) header_dict = {} header, _ = data.split(r'\r\n\r\n', 1) for line in header.split(r'\r\n')[1:]: key, val = line.split(': ', 1) header_dict[key] = val if 'Sec-WebSocket-Key' not in header_dict: logging.error('This socket is not websocket, client close.') conn.close() return magic_key = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' sec_key = header_dict['Sec-WebSocket-Key'] + magic_key key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding='utf-8')).digest()) key_str = str(key)[2:30] logging.debug(key_str) response = 'HTTP/1.1 101 Switching Protocols\r\n' \ 'Connection: Upgrade\r\n' \ 'Upgrade: websocket\r\n' \ 'Sec-WebSocket-Accept: {0}\r\n' \ 'WebSocket-Protocol: chat\r\n\r\n'.format(key_str) conn.send(bytes(response, encoding='utf-8')) logging.debug('Send the handshake data') WebSocketThread(conn).start()
进行通信
服务端解析 WebSocket 报文
Server 端接收到 Client 发来的报文需要进行解析
Client 包格式
FIN: 占 1bit
0:不是消息的最后一个分片
1:是消息的最后一个分片
RSV1, RSV2, RSV3:各占 1bit
一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非
0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错。
Opcode: 4bit
%x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
%x1:表示这是一个文本帧(text frame);
%x2:表示这是一个二进制帧(binary frame);
%x3-7:保留的操作代码,用于后续定义的非控制帧;
%x8:表示连接断开;
%x9:表示这是一个心跳请求(ping);
%xA:表示这是一个心跳响应(pong);
%xB-F:保留的操作代码,用于后续定义的控制帧。
Mask: 1bit
表示是否要对数据载荷进行掩码异或操作。
0:否
1:是
Payload length: 7bit or (7 + 16)bit or (7 + 64)bit
表示数据载荷的长度
0~126:数据的长度等于该值;
126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度;
127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。
Masking-key: 0 or 4bytes
当 Mask 为 1,则携带了 4 字节的 Masking-key;
当 Mask 为 0,则没有 Masking-key。
掩码算法:按位做循环异或运算,先对该位的索引取模来获得 Masking-key 中对应的值 x,然后对该位与 x 做异或,从而得到真实的 byte 数据。
注意:掩码的作用并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)等问题。
Payload Data: 载荷数据
解析 WebSocket 报文代码如下:
def read_msg(data): logging.debug(data) msg_len = data[1] & 127 # 数据载荷的长度 if msg_len == 126: mask = data[4:8] # Mask 掩码 content = data[8:] # 消息内容 elif msg_len == 127: mask = data[10:14] content = data[14:] else: mask = data[2:6] content = data[6:] raw_str = '' # 解码后的内容 for i, d in enumerate(content): raw_str += chr(d ^ mask[i % 4]) return raw_str
服务端发送 WebSocket 报文
返回时不携带掩码,所以 Mask 位为 0,再按载荷数据的大小写入长度,最后写入载荷数据。
struct 模块解析
struct.pack(fmt, v1, v2, ...)
按照给定的格式 fmt,把数据封装成字符串 ( 实际上是类似于 C 结构体的字节流 )
struct 中支持的格式如下表:
Format | C Type | Python type | Standard size |
---|---|---|---|
x | pad byte | no value | |
c | char | bytes of length 1 | 1 |
b | signed char | integer | 1 |
B | unsigned char | integer | 1 |
? | _Bool | bool | 1 |
h | short | integer | 2 |
H | unsigned short | integer | 2 |
i | int | integer | 4 |
I | unsigned int | integer | 4 |
l | long | integer | 4 |
L | unsigned long | integer | 4 |
q | long long | integer | 8 |
Q | unsigned long long | integer | 8 |
n | ssize_t | integer | |
N | size_t | integer | |
e | -7 | float | 2 |
f | float | float | 4 |
d | double | float | 8 |
s | char[] | bytes | |
p | char[] | bytes | |
P | void * | integer |
为了同 C 语言中的结构体交换数据,还要考虑有的 C 或 C++ 编译器使用了字节对齐,通常是以 4 个字节为单位的 32 位系统,故而 struct 根据本地机器字节顺序转换。可以用格式中的第一个字符来改变对齐方式,定义如下:
Character | Byte order | Size | Alignment |
---|---|---|---|
@ | native | native | native |
= | native | standard | none |
< | little-endian | standard | none |
> | big-endian | standard | none |
! | network (= big-endian) | standard | none |
发送 WebSocket 报文代码如下:
def write_msg(message): data = struct.pack('B', 129) # 写入第一个字节,10000001 # 写入包长度 msg_len = len(message) if msg_len <= 125: data += struct.pack('B', msg_len) elif msg_len <= (2 ** 16 - 1): data += struct.pack('!BH', 126, msg_len) elif msg_len <= (2 ** 64 - 1): data += struct.pack('!BQ', 127, msg_len) else: logging.error('Message is too long!') return data += bytes(message, encoding='utf-8') # 写入消息内容 logging.debug(data) return data
总结
没有其他能像 WebSocket 一样实现全双工传输的技术了,迄今为止,大部分开发者还是使用 Ajax 轮询来实现,但这是个不太优雅的解决办法,WebSocket 虽然用的人不多,可能是因为协议刚出来的时候有安全性的问题以及兼容的浏览器比较少,但现在都有解决。如果你有这些需求可以考虑使用 WebSocket:
多个用户之间进行交互;
需要频繁地向服务端请求更新数据。
比如弹幕、消息订阅、多玩家游戏、协同编辑、股票基金实时报价、视频会议、在线教育等需要高实时的场景。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
The above is the detailed content of Analysis of WebSocket Communication Usage. For more information, please follow other related articles on the PHP Chinese website!