55

我正在尝试将整个结构从客户端传递到服务器,反之亦然。让我们假设我的结构如下

struct temp {
  int a;
  char b;
}

我正在使用sendto并发送结构变量的地址并使用recvfrom函数在另一端接收它。但我无法获得在接收端发送的原始数据。在 sendto 函数中,我将接收到的数据保存到 struct temp 类型的变量中。

n = sendto(sock, &pkt, sizeof(struct temp), 0, &server, length);
n = recvfrom(sock, &pkt, sizeof(struct temp), 0, (struct sockaddr *)&from,&fromlen);

其中 pkt 是 struct temp 类型的变量。

尽管我收到了 8 字节的数据,但如果我尝试打印它只是显示垃圾值。对修复它有什么帮助吗?

注意: 不必使用第三方库。

EDIT1:我对这个序列化概念真的很陌生..但是如果不进行序列化,我不能通过套接字发送结构吗?

EDIT2:当我尝试使用sendtorecvfrom函数发送字符串或整数变量时,我在接收端正确接收数据。为什么不是在结构的情况下?如果我不必使用序列化功能,那么我应该单独发送结构的每个成员吗?这确实不是一个合适的解决方案,因为如果有“n”个成员,那么就会添加“n”行代码来发送或接收数据。

4

7 回答 7

86

这是一个非常糟糕的主意。二进制数据应始终以以下方式发送:

永远不要以二进制方式编写整个结构,不要写入文件,不要写入套接字。

始终分别编写每个字段,并以相同的方式读取它们。

你需要有这样的功能

unsigned char * serialize_int(unsigned char *buffer, int value)
{
  /* Write big-endian int value into buffer; assumes 32-bit int and 8-bit char. */
  buffer[0] = value >> 24;
  buffer[1] = value >> 16;
  buffer[2] = value >> 8;
  buffer[3] = value;
  return buffer + 4;
}

unsigned char * serialize_char(unsigned char *buffer, char value)
{
  buffer[0] = value;
  return buffer + 1;
}

unsigned char * serialize_temp(unsigned char *buffer, struct temp *value)
{
  buffer = serialize_int(buffer, value->a);
  buffer = serialize_char(buffer, value->b);
  return buffer;
}

unsigned char * deserialize_int(unsigned char *buffer, int *value);

或者等价的,当然有几种方法可以设置缓冲区管理等。然后您需要执行序列化/反序列化整个结构的更高级别的函数。

这假设序列化是从缓冲区完成的,这意味着序列化不需要知道最终目标是文件还是套接字。这也意味着您需要支付一些内存开销,但出于性能原因,这通常是一个不错的设计(您不想将每个值都写入套接字)。

完成上述操作后,以下是序列化和传输结构实例的方法:

int send_temp(int socket, const struct sockaddr *dest, socklen_t dlen,
              const struct temp *temp)
{
  unsigned char buffer[32], *ptr;

  ptr = serialize_temp(buffer, temp);
  return sendto(socket, buffer, ptr - buffer, 0, dest, dlen) == ptr - buffer;
}

关于上述几点需要注意:

  • 要发送的结构首先被逐个字段序列化为buffer.
  • 序列化例程返回一个指向缓冲区中下一个空闲字节的指针,我们用它来计算它序列化到多少字节
  • 显然,我的示例序列化例程不能防止缓冲区溢出。
  • 如果调用成功则返回值为 1 sendto(),否则返回值为 0。
于 2009-10-16T09:52:46.953 回答
11

使用“pragma”包选项确实解决了我的问题,但我不确定它是否有任何依赖项??

#pragma pack(1)   // this helps to pack the struct to 5-bytes
struct packet {
int i;
char j;
};
#pragma pack(0)   // turn packing off

然后以下代码行运行良好,没有任何问题

n = sendto(sock,&pkt,sizeof(struct packet),0,&server,length);

n = recvfrom(sock, &pkt, sizeof(struct packet), 0, (struct sockaddr *)&from, &fromlen);
于 2009-10-20T10:23:24.053 回答
9

无需为整数类型编写自己的序列化例程short-long使用htons()/ htonl()POSIX 函数。

于 2009-10-16T11:26:36.140 回答
6

如果您不想自己编写序列化代码,请找到合适的序列化框架并使用它。

也许谷歌的协议缓冲区是可能的?

于 2009-10-16T10:12:25.057 回答
1

序列化是个好主意。您还可以使用Wireshark监控流量并了解数据包中实际传递的内容。

于 2009-10-16T10:05:56.613 回答
0

如果您要传输的数据格式非常简单,那么与 ANSI 字符串之间的转换是简单且可移植的。

于 2009-10-16T12:10:55.110 回答
0

无需序列化并依赖于 3rd 方库,它很容易使用标签、长度和值提出原始协议。

Tag: 32 bit value identifying the field
Length: 32 bit value specifying the length in bytes of the field
Value: the field

根据需要连接。对标签使用枚举。并使用网络字节顺序...

易于编码,易于解码。

此外,如果您使用 TCP,请记住它是数据,因此如果您发送例如 3 个数据包,您不一定会收到 3 个数据包。它们可能被“合并”到一个流中,具体取决于 nodelay/nagel 算法等等,你可以将它们全部放在一个 recv 中......你需要例如使用 RFC1006 来分隔数据。

UDP 更容易,您会为每个发送的数据包收到一个不同的数据包,但它的安全性要低得多。

于 2009-10-16T11:35:14.047 回答