在python中封装套接字数据的正确方法?

时间:2013-03-06 22:41:27

标签: python sockets

我正在开发一个通过套接字向另一个实例发送和接收数据的应用程序,我很好奇使用“END”标记封装数据的最有效方法。例如,以下是两个用于在套接字连接上进行读写的函数:

def sockWrite(conn, data):
    data = data + ":::END"
    conn.write(data)

def sockRead(conn):
    data = ""
    recvdata = conn.read()
    while recvdata:
        data = data + recvdata
        if data.endswith(':::END'):
            data = data[:len(data)-6]
            break
        recvdata = conn.read()
    if data == "":
        print 'SOCKR: No data')
    else:
        print 'SOCKR: %s', data)
    return data

我基本上在写入时添加“::: END”,因为这次单次写入可能会发生多次读取。因此,读取循环直到它达到“::: END”。

如果数据变量包含恰好在其中一个读取结束时出现的字符串“::: END”,这当然会导致问题。

是否有适当的方法以尽可能少的带宽添加来封装数据?我曾考虑过pickle或json,但担心会增加大量的带宽,因为我相信它们会将二进制数据转换为ASCII。我对此是否正确?

谢谢, 本

1 个答案:

答案 0 :(得分:0)

Zeroth:你真的需要对此进行优化吗?

通常您发送相对较小的消息。当您查看忽略的以太网,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;你必须使用partitionsplit之类的东西,并编写可以处理多个消息的代码,并编写可以存储部分消息的代码,直到下一次通过read循环。< / p>


同时,针对您的具体问题:

  

是否有一种以尽可能少的带宽添加来封装数据的正确方法?

当然,至少有三种正确的方式:分隔符,前缀或自定义格式。

你已经找到了第一个。以及它的问题:除非你的数据中永远不会出现一些字符串(例如,'\0'在人类可读的UTF-8文本中),否则你不能选择不需要转义的分隔符。

像JSON这样的自定义格式是最简单的解决方案。当最后打开的支架/支架关闭时,消息结束,下一个消息时间结束。

或者,您可以为每封邮件添加包含长度的标头。这就是许多低级协议(如TCP)所做的事情。最简单的格式之一是netstring,其中标头只是以字节为单位的长度,表示为正常的基数为10的字符串,后跟冒号。 netstring协议使用逗号作为分隔符,这增加了一些错误检查。


  

我曾考虑过pickle或json,但担心会增加大量的带宽,因为我相信他们会将二进制数据转换为ASCII

pickle包含二进制和文本格式。正如the documentation所述,如果您使用协议23HIGHEST_PROTOCOL,您将获得合理有效的二进制格式。

另一方面,JSON只处理字符串,数字,数组和字典。您必须手动将任何二进制数据呈现为字符串(或字符串或数字或其他数组),然后才能对其进行JSON编码,然后在另一方面进行反转。两种常见的方法是base-64和hex,它们分别增加了25%和100%的数据大小,但如果你真的需要,还有更有效的方法。

当然,JSON协议本身使用的字符多于严格必要的字符,所有这些引号和逗号等等,以及您为任何字段提供的任何名称都将作为未压缩的UTF-8发送。您始终可以使用BSONProtocol BuffersXDR或其他序列化格式替换JSON,如果这确实是一个问题,那么这些格式就不那么“浪费”了。

同时,pickle不是自我划分的。您必须首先拆分消息,然后再拆开它们。 JSON 自我分隔,但除非您先将消息分开,否则不能只使用json.loads;你必须写一些更复杂的东西。最简单的方法是在缓冲区上重复调用raw_decode,直到得到一个对象。