如何解决Python Socket构建的WebSocket服务器长数据(图片Base64)解析问题?
WebSocket长数据解析失败问题解决
先搞懂WebSocket帧格式的核心细节
WebSocket帧的关键结构(简化版,聚焦长数据相关部分):
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 ... | +---------------------------------------------------------------+
和长数据解析相关的核心点:
- Payload len字段:7位值如果是126,后面跟16位无符号整数表示真实长度;如果是127,后面跟64位无符号整数(大端字节序,高32位为0)。很多自研服务器只处理0-125的短长度,直接忽略126/127的扩展逻辑,这是长数据解析失败的头号原因。
- MASK位:客户端发送的帧必须置1,服务器要用后续4字节掩码对整个payload逐字节异或解密。如果只解密部分数据,或者没处理完整的长payload,会直接导致乱码。
- FIN位:长数据可能被分片发送,FIN=0表示当前是中间帧,FIN=1表示最后一帧。服务器必须拼接所有分片的payload才能得到完整数据,忽略分片逻辑会导致长数据收不全。
针对Python Socket服务器的修复要点
结合常见的自研服务器漏洞,对应修复:
- 处理扩展长度逻辑
读取7位payload len后,根据值判断是否需要读取扩展长度:import struct def get_payload_length(sock): payload_len = ord(sock.recv(1)) & 0x7F if payload_len == 126: # 读取2字节大端无符号整数 payload_len = struct.unpack('!H', sock.recv(2))[0] elif payload_len == 127: # 读取8字节大端无符号整数 payload_len = struct.unpack('!Q', sock.recv(8))[0] return payload_len - 确保完整接收并解密payload
socket.recv(n)可能只返回部分数据,长数据场景下必须循环接收至指定长度,再用掩码完整解密:def recv_all(sock, length): data = b'' while len(data) < length: chunk = sock.recv(length - len(data)) if not chunk: return None data += chunk return data # 解密逻辑 mask = recv_all(sock, 4) payload_len = get_payload_length(sock) payload = recv_all(sock, payload_len) decoded_payload = bytearray() for i in range(payload_len): decoded_payload.append(payload[i] ^ mask[i % 4]) - 处理分片帧拼接
持续接收直到收到FIN=1的帧,拼接所有分片的payload:def receive_full_message(sock): full_payload = bytearray() while True: first_byte = ord(sock.recv(1)) fin = (first_byte & 0x80) != 0 opcode = first_byte & 0x0F # 只处理文本(0x1)、二进制(0x2)和续帧(0x0),忽略ping/pong等控制帧 if opcode not in [0x0, 0x1, 0x2]: continue payload_len = get_payload_length(sock) mask = recv_all(sock, 4) payload = recv_all(sock, payload_len) # 解密当前帧payload decoded = bytearray() for i in range(payload_len): decoded.append(payload[i] ^ mask[i % 4]) full_payload.extend(decoded) if fin: break # 文本帧转字符串,二进制帧返回字节数组 return full_payload.decode('utf-8') if opcode in [0x1, 0x0] else full_payload
测试验证
用Base64图片数据测试时,先确认客户端发送的是单帧还是分片帧:
- 单帧场景:检查服务器是否正确处理了126/127的扩展长度
- 分片场景:检查服务器是否拼接了所有FIN=0的中间帧payload
内容的提问来源于stack exchange,提问作者keimore
相关产品推荐
相关产品推荐

