【问题标题】:Passing a structure through Sockets in C通过 C 中的套接字传递结构
【发布时间】:2010-12-07 07:45:29
【问题描述】:

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

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

【问题讨论】:

  • 您可以发布您的发送/接收代码吗?
  • 你为什么要恢复我的编辑来纠正奇怪的双问号?
  • 不要使用结构体作为网络协议。使用网络协议作为网络协议。以八位字节设计您的协议,并为自己编写一个库来发送和接收它。或者使用现有的,例如 DML、XDR、... 使用 structs 会引入至少六个您甚至可能不知道的依赖项,并会导致类似这样的进一步问题。
  • @EJP - 我同意你的看法

标签: c sockets recv sendto


【解决方案1】:

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

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

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

你需要有类似的功能

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。
  • 序列化例程返回一个指向缓冲区中下一个空闲字节的指针,我们用它来计算它序列化到多少字节
  • 显然我的示例序列化例程不能防止缓冲区溢出。
  • 如果sendto()调用成功,返回值为1,否则返回0。

【讨论】:

  • 在这种情况下,int 在不同的机器上也可以有不同的大小。
  • @Douglas:绝对正确,并添加了列表。谢谢!
  • @unwind - serialize_int 和 deserialize_int 是标准函数吗??
  • @codingfreak:不,你需要定义它们来做你需要的那种序列化。
  • @unwind - 我没有得到你... ??我应该只发送单个成员而不是通过套接字一次发送整个结构吗??
【解决方案2】:

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

#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);

【讨论】:

【解决方案3】:

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

【讨论】:

  • @qrdl:我的功能也是如此,如文档所述。它将始终序列化为大端。您当然也可以使用 htonX()/ntohX() 函数,但这试图说明更通用的方法。
  • @codingfreak 我没有说要在结构上使用它。这是序列化短整数或长整数的正确方法,就是这样
  • @qrdl - 但我的问题是如何通过套接字发送结构......即使不使用 htons()/htonl() 函数,我也能够使用 sendto() 和recvfrom() 函数.....
  • @qrdl:我的函数对主机的字节顺序没有任何假设。
  • @unwind 我的错。对不起,我一直忘记 shift 是字节顺序感知的。我会换个帖子
【解决方案4】:

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

也许谷歌的protocol buffers 是可能的?

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      无需序列化并依赖于 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 更简单,发送的每个数据包都会收到一个不同的数据包,但它的安全性要低得多。

      【讨论】:

      • 目前我使用的是recvfrom和sendto,一般在UDP通信的情况下使用....
      • 是的,环回或可靠连接会很好。
      • 有很多。 Google Protocols, Apache Thrift, ASN.1, CSN.1, JSON, XML ...根据您的应用程序,您可以摆脱您正在做的事情,它会工作得很好......它只是不是很健壮!
      • 我已经在问题中提到...没有第三方的东西只是 libc 库...
      • ASN.1、CSN.1、JSON、XML 如果您选择不自己做,则只依赖第三方的东西。标记是谷歌协议、Apache Thrift、ASN.1 和 CSN.1 的最基本构建块,而且它的工作很简单。你也可以看看这个:liw.iki.fi/liw/texts/cpp-trick.html 虽然它忽略了字节顺序问题。
      【解决方案7】:

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

      【讨论】:

      • @mandrill - 假设它是一个复杂的——如果可能的话,一个通用的解决方案将是最好的答案...... ??
      • 如果格式更复杂,那么我会遵从本线程其他地方给出的卓越解决方案!或者序列化为更通用的东西,例如 XML 或类似 SOAP 的包装器。
      • 最终目标是以可移植的方式序列化您的数据,因此与字符串的转换是可移植的、简单的和可读的。它可能不是最安全或最有效的方法,但它不需要任何第三方库。
      • 如何将字符串重新转换回可以放入结构中的数据......我觉得会更复杂??
      • 要将字符串重新转换回数据,您必须解析字符串,使用 atof()、atoi()、sscanf() 等(也许不是 sscanf() , 这很危险)。没错,解析字符串对于非简单数据可能会变得复杂。我建议改用 3rd 方序列化库。
      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 2015-05-31
      • 1970-01-01
      • 2016-06-06
      • 2015-01-02
      • 2013-07-22
      • 2016-01-11
      相关资源
      最近更新 更多