在 Python 中编写客户端服务器应用程序时,可能会遇到丢失数据的问题。例如,在客户端尝试读取数据时,服务器可能已经发送了多条消息,如果这些消息都在同一个 4k 缓冲区内,那么 recv() 调用将获取这两条消息。但是,如果 getMessage 代码只执行类似 pickle.loads(msg) 的操作,那么它只会获取第一条消息,而丢弃其余消息,从而导致数据丢失。另外,如果在读取时缓冲区中有多于 4096 字节的数据,也可能会遇到另一个问题,因为可能会获取到一条消息的片段,从而导致解 pickle 时出错。
- 解决方案 为了解决丢失数据的问题,需要将收到的字符串拆分成单独的消息,或者更简单的方法是将套接字视为流,并让 pickle.load 从中提取单个消息。
以下是解决该问题的代码示例:
# 将字符串拆分成单独的消息
def split_messages(data):
messages = []
while data:
# 查找 pickle 头的长度
header_length = pickle.HIGHEST_PROTOCOL + 4
# 如果数据长度小于等于头长度,则说明没有更多消息
if len(data) <= header_length:
break
# 读取消息长度
message_length = pickle.loads(data[:header_length])
# 如果消息长度大于等于剩余数据长度,则说明数据不完整
if message_length > len(data) - header_length:
break
# 读取消息
message = data[header_length:header_length + message_length]
# 将消息添加到列表中
messages.append(message)
# 从数据中移除已处理的消息
data = data[header_length + message_length:]
return messages
# 使用 pickle.load 从套接字流中提取单个消息
def recv_message(sock):
data = b''
while True:
# 尝试从套接字中读取数据
try:
chunk = sock.recv(4096)
except socket.error:
# 如果遇到错误,则停止读取
break
# 如果没有读取到数据,则说明连接已关闭
if not chunk:
break
# 将数据添加到缓冲区
data += chunk
# 尝试从缓冲区中提取消息
try:
message = pickle.load(BytesIO(data))
except pickle.UnpicklingError:
# 如果遇到解 pickle 错误,则继续读取数据
continue
# 返回消息
return message
# 使用 pickle.load 从套接字流中提取多个消息
def recv_messages(sock):
messages = []
while True:
# 尝试从套接字中读取数据
try:
chunk = sock.recv(4096)
except socket.error:
# 如果遇到错误,则停止读取
break
# 如果没有读取到数据,则说明连接已关闭
if not chunk:
break
# 将数据添加到缓冲区
data += chunk
# 将数据拆分成消息
messages += split_messages(data)
# 从缓冲区中移除已处理的消息
data = data[len(messages[-1]):]
return messages