我正在通过TCP与一台实验室设备进行通信。设备具有命令集,并将回复每个命令,并确认已收到命令以及命令中请求的任何数据。问题是,在socket.recv()
命令之后,当使用send()
或其任何变体来获取设备的响应时,该方法似乎在任何时候返回收到数据,而不是收到我想要/期望的所有数据。这会导致某些数据不在我期望的recv()
通话中,而是出现在下一个通话中。
我正在考虑的一个解决方案是从发送的数据中完全单独地/异步地处理接收的数据,并在使用重复的recv()
调用时对其进行解析,但是当我想象可能会有很多开销时是一个简单的方法来使用我所知道的收到的数据(例如,它总是以回车和换行结束,但我不知道消息有多长)等待收到整个消息而不再。
总结一下:是否存在以更加可控的方式通过TCP接收数据的现有方式,以便数据在我期望的地方结束?
答案 0 :(得分:2)
TCP sockets are streams of bytes, not streams of messages.。如果你想要一个消息流,你必须在其上定义一个协议,以及处理该协议中发送和接收数据的代码。
如果您的消息都是字符串,并且从不包含换行符,那么最简单的协议可能只是将消息与换行符分开。我认为你已经解决了这个问题,你只需要知道如何实现它。
如果您处理网络的方式是阻塞recv
(无论是在程序的主循环中,还是在专用于读取套接字的线程的循环中),都内置支持此协议:使用适当的模式调用sock.makefile
(r
加上编码,如果你想要消息的Unicode字符串,rb
,如果你想要原始字节),你可以像使用它一样使用它文件 - 例如,for msg in file:
循环或file.readline()
上的while循环,直到您得到异常(意味着套接字错误)或空字符串(意味着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' (这样你也可以得到错误处理和优化)。
答案 1 :(得分:0)
根据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