第零:你真的需要优化呢?
通常你会发送相对较小的消息。当您查看您忽略了多少以太网,IP和TCP开销以及吞吐带宽的RTT时,从512字节消息中删除60个字节通常很愚蠢。
另一方面,当你的是发送巨大的消息时,通常不需要在同一个连接上发送多个消息。
看看像HTTP,IMAP等常见的互联网协议。他们大多数使用线分界,人类可读,易调试的纯文本。 HTTP可以以二进制形式发送“剩余的消息”,但是在发送完成后关闭套接字。
99%的时间,这是够好的。如果你认为它不够好,我会仍然写你的协议的文本版本,然后添加一个可选的二进制版本,一旦你有一切调试和工作(然后测试,看看是否它真的有所作为)。
同时,您的代码有两个问题。
首先,如您所知,如果您使用":::END"
作为分隔符,并且您的消息可以在其数据中包含该字符串,则您有不明确之处。解决这个问题的常用方法是某种形式的转义或引用。对于一个非常简单的例子:
def sockWrite(conn, data):
data = data.replace(':', r'\:') + ":::END"
conn.write(data)
现在在读出侧,你刚才拉过的分隔符,然后replace('r\:', ':')
上的消息。 (当然,使用6字节':::END'
分隔符来逃避每个冒号是很浪费的 - 你可能只需要使用一个非转义的冒号作为分隔符,或者写一个更复杂的转义机制。)
第二,你正确的是“这次单次写入可能会发生多次读取” - 但是对于这次单次读取也可能发生多次写入。你可以阅读这个消息的一半,再加上下一个消息的一半。这意味着你不能只使用endswith
;您必须使用类似partition
或split
的东西,并编写可处理多条消息的代码,并编写可存储部分消息的代码,直至下一次通过read
循环。
同时,为您的具体问题:
有封装的数据带宽增加尽可能最小的一个适当的方式?
当然,至少有三种适当的方式:分隔符,前缀或自定义格式。
您已经找到第一个。与它的问题:除非有一些字符串永远不会出现在你的数据中(例如,用人类可读的UTF-8文本,'\0'
),你可以选择的分隔符不需要转义。
像JSON这样的自定义格式是最简单的解决方案。当最后一个打开的支架/支架关闭时,信息结束,下一个时间到了。
或者,您可以为每个消息添加一个包含长度的标头。这是许多低级协议(如TCP)所做的。其中最简单的格式是netstring,其中标题只是以字节为单位的长度,表示为普通的10位字符串,后跟冒号。网络协议也使用逗号作为分隔符,它添加了一些错误检查。
我曾想过咸菜或JSON,但担心会增加带宽的显著量,因为我相信他们会二进制数据转换为ASCII
pickle
有二进制文件和文本格式。正如the documentation解释,如果您使用协议2
,3
或HIGHEST_PROTOCOL
,您将得到一个相当有效的二进制格式。另一方面,JSON只处理字符串,数字,数组和字典。您必须手动将任何二进制数据呈现为字符串(或字符串或数字或任何其他数组),然后才能对其进行JSON编码,然后在另一侧进行相反的处理。执行此操作的两种常用方法是base-64和hex,它们分别为数据大小增加25%和100%,但如果您真的需要,则可以使用更高效的方法。
当然,JSON协议本身会使用比严格必要的更多的字符,所有这些引号和逗号等等,以及您给任何字段的任何名称都会以未压缩的UTF-8格式发送。如果确实存在问题,您可以始终使用BSON,Protocol Buffers,XDR或其他较不“浪费”的序列化格式替换JSON。
同时,pickle
不是自行划分的。你必须首先拆分消息,然后才能取消它们。 JSON 是自定义分隔符,但除非先分开消息,否则不能只使用json.loads
;你将不得不写更复杂的东西。最简单的工作是在缓冲区上重复调用raw_decode
,直到获得一个对象。