套接字编程Python:如何确保接收到整个消息?

时间:2019-06-28 22:23:50

标签: python sockets tcp

我正在使用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个字符时,是否不能确保我会收到所有消息?

2 个答案:

答案 0 :(得分: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,我不保证每个选项都可以存在。第一个和第三个应该在任何地方工作。我还没有真正建立基准来进行比较和对比。

答案 1 :(得分:1)

对于发送,仅在将套接字置于非阻塞模式时才真正需要该循环。如果套接字处于阻止模式(默认),则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个字符。