9

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

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.

谢谢你。

4

6 回答 6

5

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

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;
x.v   = 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) = x.v; // 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);
y.v = *((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(值)

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

编辑

我将根据评论中的要求列出一些澄清。

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

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

最后,就“结构中的结构”而言,每个结构都需要多个函数。也就是说,您可以重用这些功能。例如,如果我有两个结构ABwhere Acontains 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
}

所以你应该能够为每个结构使用一个序列化方法。

于 2013-03-29T17:29:46.173 回答
4

这个答案除了你的问题之外malloc

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

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

您可能想要做的另一件协议的事情是将分配给灵活数组成员的大小s写入struct B. 无论您为序列化数据选择何种布局,显然双方都应该尊重。

重要的是要注意,您不能依赖任何特定于机器的东西,例如字节顺序、结构填充或基本类型的大小。这意味着您应该分别序列化元素的每个字段并为其分配固定数量的字节。

于 2013-03-29T17:17:25.767 回答
1

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

这是一个使用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);
于 2016-11-26T03:45:17.123 回答
0

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

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

于 2013-03-29T17:40:49.697 回答
0

我尝试了@RageD 提供的方法,但没有奏效。

int我从反序列化中得到的值不是原来的值。

对我来说,memcpy()适用于非字符串变量。(您仍然可以使用strcpy()for char *

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct A {
    int a;
    char *str;
} test_struct_t;

char *serialize(test_struct_t t) {
    int str_len = strlen(t.str);

    int size = 2 * sizeof(int) + str_len;
    char *buf = malloc(sizeof(char) * (size+1));

    memcpy(buf, &t.a, sizeof(int));
    memcpy(buf + sizeof(int), &str_len, sizeof(int));
    memcpy(buf + sizeof(int) * 2, t.str, str_len);
    buf[size] = '\0';

    return buf;
}

test_struct_t deserialize(char *buf) {
    test_struct_t t;

    memcpy(&t.a, buf, sizeof(int));

    int str_len;
    memcpy(&str_len, buf+sizeof(int), sizeof(int));

    t.str = malloc(sizeof(char) * (str_len+1));
    memcpy(t.str, buf+2*sizeof(int), str_len);
    t.str[str_len] = '\0';

    return t;
}

int main() {
    char str[15] = "Hello, world!";

    test_struct_t t;
    t.a = 123;
    t.str = malloc(strlen(str) + 1);
    strcpy(t.str, str);
    printf("original values: %d %s\n", t.a, t.str);

    char *buf = serialize(t);
    test_struct_t new_t = deserialize(buf);
    printf("new values: %d %s\n", new_t.a, new_t.str);

    return 0;
}

上面代码的输出是:

original values: 123 Hello, world!
new values: 123 Hello, world!
于 2020-01-25T22:24:32.457 回答
-1

@Shahbaz 是对的,我认为您实际上想要这个

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

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

于 2013-03-29T17:10:06.693 回答