TCP 收到的数据偶尔会错位

TCP received data sporadically misplaced

我正在通过 TCP 与一台实验室设备通信。设备有一个命令集,并且将通过确认收到命令和命令中请求的任何数据来回复每个命令。问题是,当使用 socket.recv() 或其任何变体在我 send() 命令后从设备获得响应时,方法 returns当收到 any 数据时,而不是收到 所有数据时 want/expect。这会导致某些数据不在我预期的 recv() 调用中,而是显示在下一个调用中。

我正在考虑的一个解决方案是完全 separately/asynchronously 从发送的数据中处理接收到的数据,并在使用重复 recv() 调用时解析它,但这似乎是一个很大的开销我想可能有一种简单的方法来使用我对接收到的数据的了解(例如它总是以回车 return 和换行结束,但我不知道消息有多长)等待直到整个消息已收到 ,不再

总而言之:是否有一种现有方法可以通过 TCP 以更受控的方式接收数据,以便数据在我预期的位置结束?

TCP sockets are streams of bytes, not streams of messages.。如果你想要一个消息流,你必须在它之上定义一个协议,以及在该协议中处理发送和接收数据的代码。

如果您的消息都是字符串,并且从不包含换行符,那么最简单的协议可能就是用换行符分隔消息。我想你已经解决了,你只需要知道如何实现它。

如果您处理网络的方式是阻塞的 recv(无论是在程序的主循环中,还是在专用于读取套接字的线程循环中),内置支持此协议:使用适当的模式调用 sock.makefiler 加上编码,如果你想要消息的 Unicode 字符串,rb 如果你想要原始字节),你可以使用它像一个文件——例如,一个 for msg in file: 循环,或者一个 file.readline() 的 while 循环,直到你得到一个异常(意味着套接字错误)或空字符串(意味着 EOF——一个干净的套接字关闭)。

如果您的邮件可以包含换行符,您仍然可以使用它。只是在发送之前转义消息(可能使用完整的 backslash-escape 这样它们总是可读的,以便于调试,或者可能只是 msg.replace('\', '\\').replace('\n', '\n'))在发送之前,并在接收时取消转义。

在幕后,这与普通文件对象处理磁盘文件的方式相同:当您请求下一行时,如果缓冲区中已经有完整的一行,它只是将其拆分并 returns它;如果没有,它会读取缓冲区并将它们附加到它所拥有的内容上,直到它最终得到一个换行符,然后拆分出第一个完整的行并将它 returns 给你。因此,如果第一个数据包包含换行符,它永远不会阻塞等待两个数据包。但它也永远不会给你一个“尚未完成的消息”来处理;它会一直阻塞,直到它读取足够的数据包以获得下一个换行符。

在某个时候学习如何从头开始构建这样的东西是值得的——但与此同时,你可以只使用已经存在的东西。如果您有兴趣,简短版本(没有良好的错误处理和一些有用的优化)看起来像这样:

def messages(sock):
    buf = b''
    while True:
        data = sock.recv(8192)
        if not data: break
        buf += data
        lines = buf.split('\n')
        for line in lines[:-1]:
            yield line.decode('utf8')
        buf = lines[-1]
    # Should leftover bytes after the last newline be a message, an error, or ignored? Picking arbitrarily...
    if buf: yield buf.decode('utf8')

当然,直接调用 'makefile' 更简单(这样您也可以获得错误处理和优化)。

根据 abarnert 的建议,我可以使用我对接收到的数据的了解来构建它。具体来说,我正在与之交谈的东西向我扔了很多我不想要的垃圾线,所以我只是在每一行中搜索我知道与我关心的内容相关的子字符串:

def send_message_return_response(sock, sock_file, message, substring):
   #discard remainders from commands I sent but didn't read back due to not caring
   sock_file.flush()
   sock.send(message)
   response = ''
   while substring not in response: response = sock_file.readline()
   return response