如何在c中序列化结构?

我有一个struct对象,包含几个原始数据类型,指针和结构指针。 我想通过套接字发送它,以便它可以在另一端使用。 由于我想预先支付序列化成本,如何初始化该结构的对象,以便可以立即发送而无需编组? 例如

struct A { int i; struct B *p; }; struct B { long l; char *s[0]; }; struct A *obj; // can do I initialize obj? int len = sizeof(struct A) + sizeof(struct B) + sizeof(?); obj = (struct A *) malloc(len); ... write(socket, obj, len); // on the receiver end, I want to do this char buf[len]; read(socket, buf, len); struct A *obj = (struct A *)buf; int i = obj->i; char *s = obj->p->s[0]; int i obj.i=1; obj.p. 

谢谢。

这个答案除了你的malloc的问题。

不幸的是,你找不到一个仍然与标准兼容的好技巧 。 正确序列化结构的唯一方法是分别将每个元素分解为字节,将它们写入unsigned char数组,通过网络发送它们并将这些元素放回到另一端。 简而言之,您需要进行大量的移位和按位操作。

在某些情况下,您需要定义一种协议。 例如,在您的情况下,您需要确保始终将对象p指向struct A后面,因此一旦恢复,您可以正确设置指针。 大家已经说过你不能通过网络发送指针吗?

您可能想要做的另一个协议事项是在struct B编写为灵活数组成员分配的大小。 无论您选择的序列化数据的布局如何,显然双方都应该尊重。

请务必注意,您不能依赖任何特定于机器的内容,例如字节顺序,结构填充或基本类型的大小。 这意味着您应该单独序列化元素的每个字段并为它们分配固定的字节数。

最简单的方法可能是分配一块内存来保存所有内容。 例如,考虑如下结构:

 typedef struct A { int v; char* str; } our_struct_t; 

现在, 最简单的方法是创建一个定义的格式并将其打包成一个字节数组。 我将尝试展示一个例子:

 int sLen = 0; int tLen = 0; char* serialized = 0; char* metadata = 0; char* xval = 0; char* xstr = 0; our_struct_t x; xv = 10; x.str = "Our String"; sLen = strlen(x.str); // Assuming null-terminated (which ours is) tLen = sizeof(int) + sLen; // Our struct has an int and a string - we want the whole string not a mem addr serialized = malloc(sizeof(char) * (tLen + sizeof(int)); // We have an additional sizeof(int) for metadata - this will hold our string length metadata = serialized; xval = serialized + sizeof(int); xstr = xval + sizeof(int); *((int*)metadata) = sLen; // Pack our metadata *((int*)xval) = xv; // Our "v" value (1 int) strncpy(xstr, x.str, sLen); // A full copy of our string 

因此,此示例将数据复制到大小为2 * sizeof(int) + sLen的数组中,这允许我们使用单个整数的元数据(即字符串长度)和结构中提取的值。 要反序列化,您可以想象如下:

 char* serialized = // Assume we have this char* metadata = serialized; char* yval = metadata + sizeof(int); char* ystr = yval + sizeof(int); our_struct_t y; int sLen = *((int*)metadata); yv = *((int*)yval); y.str = malloc((sLen + 1) * sizeof(char)); // +1 to null-terminate strncpy(y.str, ystr, sLen); y.str[sLen] = '\0'; 

如您所见,我们的字节数组是明确定义的。 下面我详细介绍了结构:

  • 字节0-3:元数据(字符串长度)
  • 字节4-7:Xv(值)
  • 字节8 – sLen:X.str(值)

如果遵循定义的约定,这种定义良好的结构允许您在任何环境中重新创建结构。 现在,要通过套接字发送此结构取决于您如何开发协议。 您可以先发送一个整数数据包,其中包含您刚构建的数据包的总长度,或者您可以预期首先/单独发送元数据(逻辑上分开,技术上仍然可以同时发送),然后您知道在客户端接收多少数据。 例如,如果我收到10元数据值,那么我可以期望跟随sizeof(int) + 10个字节来完成结构。 通常,这可能是14个字节。

编辑

我将按照评论中的要求列出一些澄清。

我做了字符串的完整副本,因此它在(逻辑上)连续的内存中。 也就是说,我的序列化数据包中的所有数据实际上都是完整数据 – 没有指针。 这样,我们就可以通过套接字发送一个缓冲区(我们称之为serialized )。 如果只是发送指针,接收指针的用户将期望该指针是有效的存储器地址。 但是,您的内存地址不太可能完全相同。 但是,即使他们是,他也不会像你那样在该地址获得相同的数据(除非在非常有限和特殊的情况下)。

希望通过查看反序列化过程(这是接收方)来更清楚地说明这一点。 请注意我如何分配结构来保存发件人发送的信息。 如果发送方没有向我发送完整的字符串,而只是发送内存地址,我实际上无法重建已发送的数据(即使在同一台机器上,我们有两个不同的虚拟内存空间也不相同)。 所以从本质上讲,指针只是发起者的良好映射。

最后,就结构中的“结构”而言,您需要为每个结构提供多个函数。 也就是说,您可以重用这些function。 例如,如果我有两个结构AB ,其中A包含B ,我可以有两个序列化方法:

 char* serializeB() { // ... Do serialization } char* serializeA() { char* B = serializeB(); // ... Either add on to serialized version of B or do some other modifications to combine the structures } 

因此,您应该能够为每个结构使用单个序列化方法。

解释您的数据并了解您要序列化的内容。 您希望序列化一个整数和B类型的结构(递归地,您想序列化一个int,一个long和一个字符串数组)。 然后序列化它们。 你需要它的长度sizeof(int)+ sizeof(long)+Σstrlen(s [i])+ 1。

另一方面,序列化是一个已解决的问题(实际上是多次)。 你确定需要手写一个序列化程序吗? 为什么不使用D-Bus或简单的RPC调用? 请考虑使用它们。

您应该以独立于平台的方式序列化数据。

以下是使用Binn库(我的创建)的示例:

  binn *obj; // create a new object obj = binn_object(); // add values to it binn_object_set_int32(obj, "id", 123); binn_object_set_str(obj, "name", "Samsung Galaxy Charger"); binn_object_set_double(obj, "price", 12.50); binn_object_set_blob(obj, "picture", picptr, piclen); // send over the network send(sock, binn_ptr(obj), binn_size(obj)); // release the buffer binn_free(obj); 

如果您不想使用字符串作为键,则可以使用使用整数作为键的binn_map。 还有对列表的支持。 您可以在另一个(嵌套结构)中插入一个结构。 例如:

  binn *list; // create a new list list = binn_list(); // add values to it binn_list_add_int32(list, 123); binn_list_add_double(list, 2.50); // add the list to the object binn_object_set_list(obj, "items", list); // or add the object to the list binn_list_add_object(list, obj); 

@Shahbaz是对的,我认为你真的想要这个

 int len = sizeof(struct A); obj = (struct A *) malloc(len); 

但是,当发送指向另一台机器的指针时,你会遇到问题,因为指针指向的地址在另一台机器上没有任何意义。