C++如何通过套接字发送结构体?

14

假设我有一个结构体:

struct person
{
    char name[10];
    int age;
};

struct car
{
    int locationX;
    int locationY;
};

struct company
{
    vector<person> employees;
    vector<car> cars;
};
例如,我想使用套接字(UDP)发送/接收整个公司。因此,只需一次发送和接收。

我该如何做到这一点?您能否给我一些代码片段?如何发送所有内容并读取所有内容。

谢谢!

8
序列化它。发送它。反序列化它。在序列化过程中,您可以选择如何编码数据。例如:谷歌的协议缓冲和 JSON。 - Adam Zalcman
2
在C/C++中,有许多不同的方法可以实现任意结构的序列化,你可以阅读一些资料,决定哪种方法最适合你的项目,并开始实施。你甚至可以自己编写实现方式。 - Justin ᚅᚔᚈᚄᚒᚔ
1
你必须定义一个序列化格式。将所有数据进行序列化。发送它。在另一端接收它,然后将其反序列化为您的数据结构。如何序列化取决于其他因素(例如它有多大以及您希望调试它有多容易)。个人建议使用读/写操作,并让底层套接字处理缓冲区(自己尝试缓冲通常是浪费时间的,因为底层套接字已经被设计为使用缓冲区)。 - Martin York
@AdamZalcman 谢谢。我能不用序列化来做吗?我的朋友告诉我可以使用(void *)将数据写入char[],然后发送它,但我不知道具体如何使用。 - JJ Liu
@JJLiu:首先,你朋友的建议在大多数情况下都是非常糟糕的。它依赖于接收程序使用相同编译器和平台进行编译,即使如此,在某些情况下也可能无法正常工作。其次,由于“vector”的存在,这种方法在本例中绝对行不通。 - Omnifarious
显示剩余4条评论
5个回答

22
你的问题表述暗示着你所需要的是这个:
company foo;
send(sockfd, &foo, sizeof(foo), 0); // DO NOT do this

这将基本上把company结构体的所有内存转储到您的套接字中。在这种情况下,这不起作用。即使它有点可行,这是一个非常糟糕的想法。它不起作用的原因是vector不直接包含数据。它们指向数据。这意味着当您将包含向量的结构体转储到套接字时,您将转储指向内存的指针,而不是指向的内容。这将导致(在最好的情况下)在接收端崩溃。

对于单个personcar对象,它可能会有些作用。它们不包含指针,因此它们的内存包含所有相关值。

在发送方:

person joe = { "Joe", 35 };
send(sockfd, &joe, sizeof(joe), 0); // may work, but is a bad idea, see below

接收方需要:

person joe;
recv(sockfd, &joe, sizeof(joe), 0);

但是,这仍然是个坏主意。它依赖于发送端和接收端具有完全相同的结构体内存布局。由于许多原因,这可能不是真实的情况。一些原因包括一个在PowerPC芯片上,另一个在Intel x86芯片上。或者一个在使用Visual Studio编译的Windows机器上,另一个在使用gcc编译的Linux机器上。或者可能有人对编译器标志进行了某些调整,导致默认的结构布局不同。有很多原因。

实际上,你应该使用像大家建议的序列化框架。我建议使用Google protocol buffers或其他人已经链接的Boost序列化框架。但还有许多其他的选择。

另一个应该提及的序列化框架是Cap'n Proto,因为它非常快(几乎和直接将结构体的内存映像倒入套接字一样快)。


9

9
正如其他人已经说过的那样,使用某种序列化库将提供最可靠(并且可能是最简单维护)的解决方案。然而,如果您真的想要自己实现它,那么下面展示了“想法”如何处理它。以下代码分配了一个缓冲区,然后用员工向量内容填充它。变量 c 假定为类型 company 。
需要注意的一些事项:
  • 示例使用缓冲区加载多个条目进行单个发送。对于UDP,通常不希望逐个发送条目,因为这将导致每个条目一个数据包。
  • 存储在缓冲区中的第一个值是项目数。实际上,可能需要一些额外的信息(例如,数据类型)。否则,接收器可能不知道它正在获取什么。
  • 字符数据被复制并以空字符结尾。另一种方法是在该数据前缀中添加长度并放弃空终止符。
  • 最好将整数值存储在4字节边界上对齐的缓冲区中(取决于系统)。
  • 使用 htonl 将整数值存储为网络字节顺序。接收端应使用 ntohl 读取它们。
简单且非常不完整的示例:
   // allocate buffer to store all the data for a send.  In a real world
   // this would need to be broken up into appropriately sized chunks
   // to avoid difficulties with UDP packet fragmentation.

   // This likely over-allocates because the structure likely has padding
   char *buf = new char[ sizeof( uint32_t ) +   // for total number of entries
                  sizeof( person ) * c.employees.size() ];  // for each entry

   char *pos = buf;
   // Indicate how many are being sent
   *(uint32_t*)pos = htonl( c.employees.size() );
   pos += sizeof uint32_t;
   for ( vector<person>::iterator pi = c.employees.begin();
         pi != c.employees.end(); pi++ )
      {
      *(uint32_t*)pos = htonl( pi->age );
      pos += sizeof uint32_t;
      strcpy( pos, pi->name );
      pos += strlen( pi->name ) + 1;
      }

   send( 0, buf, (int)( pos - buf ), 0 );


   delete [] buf;

   // The receiving end would then read the number of items and 
   // reconstruct the structure.

8
如果你需要制作许多这样的东西,可以考虑使用Thrift。它会自动生成所有必要的代码来序列化所有结构,以便你不必手动操作。
它也支持字符串,非常实用。
哦!而且它是免费的。8-)
另外,它会发送二进制数据,因此你不必将数字转换为字符串或反之。如果你的大部分数据不是字符串,那么这样做会更快。 http://thrift.apache.org/

6

请注意,某些版本的boost序列化库未能正常工作(1.37到1.42或类似版本)。此外,就其健壮性而言,我发现这个库使用起来很复杂。 - Alexis Wilke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接