1

我正在开发一个应用程序,该应用程序通过套接字向其自身的另一个实例发送和接收数据,我很好奇用“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。我说得对吗?

谢谢,本

4

1 回答 1

1

Zeroth:你真的需要优化这个吗?

通常你发送相对较小的消息。当您查看忽略了多少以太网、IP 和 TCP 开销以及占用带宽的 RTT 时,从 512 字节的消息中削减 60 字节通常是愚蠢的。

另一方面,当您发送大量消息时,通常不需要在同一连接上发送多条消息。

查看常见的 Internet 协议,如 HTTP、IMAP 等。它们中的大多数使用行分隔、人类可读、易于调试的纯文本。HTTP 可以以二进制形式发送“消息的其余部分”,但是在完成发送后关闭套接字。

99% 的时间,这已经足够了。如果您认为它在您的情况下不够好,我仍然会编写协议的文本版本,然后在您调试完所有内容并正常工作后添加一个可选的二进制版本(然后测试它是否真的有所作为)。


同时,您的代码存在两个问题。

首先,如您所知,如果您将":::END"其用作分隔符,并且您的消息可以在其数据中包含该字符串,那么您就会有歧义。解决这个问题的常用方法是某种形式的转义或引用。举一个非常简单的例子:

def sockWrite(conn, data):
    data = data.replace(':', r'\:') + ":::END"
    conn.write(data)

现在在阅读方面,您只需取消分隔符,然后replace('r\:', ':')在消息上。(当然,为了使用 6 字节的':::END'分隔符而对每个冒号进行转义是很浪费的——你不妨只使用未转义的冒号作为分隔符,或者编写更复杂的转义机制。)

其次,“单次写入可能会发生多次读取”是对的,但单次读取可能会发生多次写入也是正确的。您可以阅读此消息的一半,以及下一条消息的一半。这意味着您不能只使用endswith; 您必须使用partitionor之类的东西split,并编写可以处理多条消息的代码,还必须编写可以存储部分消息的代码,直到下一次read循环。


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

是否有适当的方法以尽可能少的带宽添加来封装数据?

当然,至少有三种合适的方式:定界符、前缀或自定界格式。

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

像 JSON 这样的自定界格式是最简单的解决方案。当最后一个打开的大括号/括号关闭时,消息就结束了,是下一个的时候了。

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


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

pickle具有二进制和文本格式。正如文档所解释的,如果您使用协议2、、3HIGHEST_PROTOCOL,您将获得相当有效的二进制格式。

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

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

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

于 2013-03-06T22:48:29.487 回答