Python 客户端服务器通信中丢失数据的问题以及解决方法

59 阅读2分钟

在 Python 中编写客户端服务器应用程序时,可能会遇到丢失数据的问题。例如,在客户端尝试读取数据时,服务器可能已经发送了多条消息,如果这些消息都在同一个 4k 缓冲区内,那么 recv() 调用将获取这两条消息。但是,如果 getMessage 代码只执行类似 pickle.loads(msg) 的操作,那么它只会获取第一条消息,而丢弃其余消息,从而导致数据丢失。另外,如果在读取时缓冲区中有多于 4096 字节的数据,也可能会遇到另一个问题,因为可能会获取到一条消息的片段,从而导致解 pickle 时出错。

  1. 解决方案 为了解决丢失数据的问题,需要将收到的字符串拆分成单独的消息,或者更简单的方法是将套接字视为流,并让 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