【问题标题】:Serializing a C struct in Python and sending over a socket在 Python 中序列化 C 结构并通过套接字发送
【发布时间】:2016-04-04 15:46:34
【问题描述】:

我正在尝试序列化以下 C 结构

struct packet
{
    int id;
    unsigned char *ce;
    unsigned char *syms;
};

在 Python 中并通过套接字发送。 cesyms 指向的元素的数量已知为 N。目前我正在这样做。首先,我使用 ctypes 将结构包装到

class Packet(Structure):
    _fields_ = [("id",  c_int),
                ("ce", POINTER(c_ubyte)),
                ("syms", POINTER(c_ubyte))]

然后,我使用来自ctypes.POINTER(Packet)fill_data 函数填充以下类的对象:

class DataLoad:
    def __init__(self):
        self.id = -1
        self.ce = []
        self.syms = []

    def fill_data(self, pkt_p):
        """ pkt_p is POINTER(Packet)
        """
        self.id = pkt_p.contents.id
        self.ce = []
        for i in range(N):
            self.ce.append(pkt_p.contents.ce[i])
        self.syms = []
        for i in range(N):
            self.syms.append(pkt_p.contents.syms[i])

最后,我只是简单地使用pickle.dumps(DataLoad) 生成一个字节流并发送。

这种方法效果很好。但是,它似乎很慢。我能看到的一个原因是pickle.dumps 带来了很多开销。例如,如果 C 结构只有 1024 个字节,我可能必须使用 pickle 为每个结构发送近 4000 个字节。此外,打包/填充 DataLoad 也需要时间。

所以我的问题是,我还有其他更好的选择来在 python 中序列化这个 C 结构并发送吗?我宁愿避免泡菜并填充一个单独的类实例。谢谢。

【问题讨论】:

  • 在 c 中执行并使用 cython?

标签: python c sockets serialization ctypes


【解决方案1】:

最后我想出了以下方法来手动序列化 `Packet' 实例而不使用 pickle。

def serialize(pkt_p, size_g, size_p):
    """ Serialize Packet instance
        size_g - number of elements pointed by ce
        size_p - number of elements pointed by syms
        Return a byte stream
    """ 
    pktstr = b''
    pktstr += struct.pack('i', pkt_p.contents.id)
    pktstr += string_at(pkt_p.contents.ce, size_g)
    pktstr += string_at(pkt_p.contents.syms, size_p)
    return pktstr

def deserialize(pkt_p, pktstr, size_g, size_p):
    """ De-serialize pktstr and fill a POINTER(Packet)
    """
    pkt_p.contents.id = struct.unpack('i', pktstr[0:4])[0]
    ce = (c_ubyte * size_g).from_buffer_copy(pktstr[4:4+size_g])
    pkt_p.contents.ce = cast(ce, POINTER(c_ubyte))
    syms = (c_ubyte * size_p).from_buffer_copy(pktstr[-size_p:])
    pkt_p.contents.syms = cast(syms, POINTER(c_ubyte))

string_at()from_buffer_copy() 函数是关键。

【讨论】:

  • 使用pktbuf = bytearray() 允许缓冲区通过+= 操作就地增长(即C realloc)。
  • 另外,在serialize 中使用struct.packstring_at 是不必要的。所有 ctypes 对象都实现了缓冲区协议。它们可以直接附加到bytearray,例如pktbuf += pkt_p[0].id。对于指针,创建一个c_char 数组而不复制 到新字符串(与string_at 不同),因此只需复制一次即可将其存储在缓冲区中,例如pktbuf += cast(pkt_p[0].ce, POINTER(c_char * size_g))[0].
  • deserialize 中,您可以使用pkt_p[0].id = c_int.from_buffer_copy(pktstr) 而不是struct.unpack。对于数组,您应该使用from_buffer_copyoffset 参数来避免切片制作的临时副本,并且您不需要干预cast,例如pkt_p[0].ce = (c_ubyte * size_g).from_buffer_copy(pktstr, sizeof(c_int))pkt_p[0].syms = (c_ubyte * size_p).from_buffer_copy(pktstr, sizeof(c_int) + size_g).
  • @eryksun,这些对代码性能非常有用的建议!学到了很多!谢谢。
【解决方案2】:

首先,如果您知道元素的数量为 N,我建议您将结构更改为:

class Packet(Structure):
    _fields_ = [("id", c_int),
                ("ce", c_ubyte * N),
                ("syms", c_ubyte * N)]

接下来,如果你只想发送结构数据,你不需要腌制整个事情。只需发送数据包数据:

p = Packet()
p.id = 555
...
# cast the struct to a pointer to a char array
pdata = ctypes.cast(ctypes.byref(p), ctypes.POINTER(ctypes.c_char * ctypes.sizeof(p)))
# now you can just save/send the struct data
someSocketObject.send(pdata.contents.raw)

读取另一端的数据:

p = Packet()
raw = someSocketObject.read(ctypes.sizeof(p))
ctypes.memmove(ctypes.pointer(p),raw,ctypes.sizeof(p))

【讨论】:

  • 感谢您的回复。虽然我知道Python中元素的个数是固定的,但是我不能改变C结构的格式,因为cesyms的长度需要在C中动态分配。在这个限制下,我还能定义吗Packet你提议的方式?
  • 可能,但这取决于。如果您在 C 程序中对原始 Packet 结构进行简单转储,它将写出一个 int,后跟两个指针。 cesyms 字段的数据将保存在其他位置。然而,大多数有用的 C 程序实际上会被编写为转储数据,而不是指针的值。如果你只是在 python 程序之间交换数据,那么你不必担心。
  • @YeLi,不,你不能使用这个Packet 定义。 POINTER(c_ubyte) 是 4 或 8 个字节,具体取决于指针大小。 c_ubyte * N 是 N 个字节。您也不能将结构作为缓冲区发送(顺便说一句,指针转换是不必要的;ctypes 对象支持缓冲区协议),因为指针在您的进程之外毫无意义。您需要创建一个新的缓冲区,其中包含id、数组大小 N 和连接的数据数组。我会使用bytearray
  • @erysun,感谢您指出这一点。是的,你是对的。我不得不手动序列化cesyms 指向的字节。
猜你喜欢
  • 2011-06-18
  • 2015-01-02
  • 2013-07-22
  • 2016-06-06
  • 1970-01-01
  • 2018-06-26
  • 1970-01-01
  • 2016-01-11
  • 1970-01-01
相关资源
最近更新 更多