【问题标题】:TCP received data sporadically misplacedTCP 接收到的数据偶尔放错了位置
【发布时间】:2018-04-05 16:32:57
【问题描述】:

我正在通过 TCP 与一台实验室设备进行通信。该设备有一个命令集,将回复每个命令,确认已收到命令以及命令中请求的任何数据。问题在于,当使用例如socket.recv() 或其任何变体在我send() 一个命令之后从设备获取响应时,该方法在any 时返回收到数据,而不是收到我想要/期望的所有数据。这会导致某些数据不在我预期的 recv() 调用中,而是出现在下一次调用中。

我正在考虑的一个解决方案是将接收到的数据与发送的数据完全分开/异步处理,并在使用重复的recv() 调用时对其进行解析,但是当我想象可能存在一个使用我对接收到的数据的了解的简单方法(例如,它总是以回车和换行结束,但我不知道消息有多长)等到收到整个消息并且不再.

总结一下:有没有一种现有的方法可以通过 TCP 以更可控的方式接收数据,以便数据在我期望的地方结束?

【问题讨论】:

  • 这就是 TCP 的工作原理。 TCP sockets are streams of bytes, not streams of messages. 如果你想要一个消息流,你必须在 recv 周围的循环(或异步回调,或其他)之上构建它,以及实现你选择的一些消息框架协议的缓冲区和代码。跨度>
  • 我只是好奇是否有预构建的方法来处理框架,可能来自我可以安装的更高级别的库或包。我不认为我所描述的是一个新问题,我只是有点惊讶我没有找到我正在寻找的东西。
  • 有一百万种协议,从简单的网络字符串或 jsonlines 到像 xmlrpc over http 这样的协议。加上六种实现协议处理的方法,这取决于您首先如何从网络中读取数据(使用或不使用线程的阻塞读取、选择循环、回调、异步......)。大多数组合都有一个标准库或第三方库来完成大部分工作,但首先你必须选择一个。

标签: python python-3.x sockets tcp buffering


【解决方案1】:

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

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

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

如果您的消息可以包含换行符,您仍然可以使用它。只需在发送前转义消息(可能使用完整的反斜杠转义,以便它们始终可读,以便于调试,或者可能只是 msg.replace('\\', '\\\\').replace('\n', '\\n')),然后在接收时转义。

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

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

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”会更简单(这样您也可以获得错误处理和优化)。

【讨论】:

    【解决方案2】:

    根据 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
    

    【讨论】:

    • 您可能会考虑将接收分离到仅产生有效响应的生成器函数中。这将允许您的发送和接收代码解耦,并可能允许更优雅地处理超时和其他问题和异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 2013-08-14
    • 2015-08-08
    • 1970-01-01
    • 2011-09-13
    相关资源
    最近更新 更多