【问题标题】:Socket Programming Python: How to make sure entire message is received?Socket Programming Python:如何确保接收到整个消息?
【发布时间】:2019-11-10 19:31:38
【问题描述】:

我正在使用 python 3.x 和套接字模块。服务器在 ipv4 地址上运行并使用 tcp。 我阅读了一些关于如何发送和接收数据的教程。为了让服务器或客户端确保发送了整个消息,您只需检查发送的数据量是否等于消息的大小:

def mysend(self, msg):
    totalsent = 0
    while totalsent < MSGLEN:
        sent = self.sock.send(msg[totalsent:])
        if sent == 0:
            raise RuntimeError("socket connection broken")
        totalsent = totalsent + sent

来源:https://docs.python.org/3/howto/sockets.html#socket-howto

为了确保客户端已收到完整的响应,this tutorial 建议在响应的开头添加响应的大小。

我的问题:

  1. 如何确保我收到了指示消息大小的消息的第一部分(假设我的消息包含 1000 个字符,我需要四个字符来指示大小)?
  2. 为什么我不能只在消息的开头添加一个指定的符号,如“”,这样我就知道它从哪里开始和结束了?

编辑:

  1. 当我使用 sock.recv(1024) 时,我的消息只有 500 到 1000 个字符,这不能确保我收到所有消息吗?

【问题讨论】:

  • 您提出了非常好的问题,并且得到了一些非常好的回答。值得强调的一点:你正在读写一个字节流,它不同于它的逻辑内容。因此,您的客户将一次读取一个缓冲区,直到没有任何内容可供读取。无论消息多长或短。第一步:1)“读取字节(并保存它们)直到你完成”第二步:2)解释你读到的内容。
  • 还可以查看socket.makefile。这是一个带有示例的答案:stackoverflow.com/a/55840026/235698

标签: python sockets tcp


【解决方案1】:

首先,发送所有字节不需要循环,因为 python 套接字提供了一个简单的方法:socket.sendall()

现在回答你的问题:

  1. 是的,即使只接收 4 个字节,您也应该有一个接收循环,在套接字上调用 recv(),直到读取 4 个字节。

  2. 您可以,如果您可以保证此类字符不会出现在消息本身中。但是,您仍然需要搜索您读入的每个字符以查找魔术分隔符,因此它似乎不如简单地在消息正文前加上一个长度。

  3. 当您调用 recv(n) 时,它只能保证返回最多 n 个字节,而不是精确的 n 个字节。

这里有三种不同的recvall() 方法可供比较:

def recvall(sock, size):
    received_chunks = []
    buf_size = 4096
    remaining = size
    while remaining > 0:
        received = sock.recv(min(remaining, buf_size))
        if not received:
            raise Exception('unexpected EOF')
        received_chunks.append(received)
        remaining -= len(received)
    return b''.join(received_chunks)

还有更短的

def recvall2(sock, size):
    return sock.recv(size, socket.MSG_WAITALL)

最后是另一个版本,它比第一个版本短一些,但缺少一些功能:

def recvall3(sock, size):
    result = b''
    remaining = size
    while remaining > 0:
        data = sock.recv(remaining)
        result += data
        remaining -= len(data)
    return result

第二个很好而且很短,但它依赖于一个套接字选项socket.MSG_WAITALL,我不相信它会保证在每个平台上都存在。第一个和第三个应该在任何地方都可以使用。我还没有真正对任何基准进行比较和对比。

【讨论】:

    【解决方案2】:

    对于发送,只有在将套接字置于非阻塞模式时才真正需要该循环。如果套接字处于阻塞模式(默认),sock.send() 将在它发送完整个消息或收到错误之前不会返回。

    但是,对于接收没有等价物,因为 TCP 在协议中不包括消息边界。 sock.recv() 会在任何数据可用时立即返回。

    1. 循环调用sock.recv(),直到获得所需的一切。与您的发送例程每次迭代发送较短子字符串的方式类似,您可以将recv() 参数的大小减少您目前已阅读的数量。所以它看起来像:
    def myrecv(self, size):
        buffer = ''
        while size > 0:
            msg = self.sock.recv(size)
            buffer += msg
            size -= len(msg)
        return buffer
    

    如果你在每条消息前加上一个 4 字节的长度,你可以这样做:

    msgsize = int(myrecv(4))
    message = myrecv(msgsize)
    
    1. 您可以这样做,但这会使事情变得更加复杂。您需要一次读取一个字符,检查分隔符,或者实现一个缓冲区来保存您已读取但尚未返回给调用者的数据,因为它已超过当前消息的末尾。此外,如果数据可以包含分隔符,则需要能够对其进行转义。

    2. 不,recv(1024) 可以在收到任何数据后立即返回,该数据可能小于已发送消息的大小。如果它保证返回 1024 个字符,如果发件人只发送 500 个字符,它就会挂起,因为它正在等待剩余的 524 个字符。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-19
      • 2016-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多