【问题标题】:How can I portably send a C struct through a network socket?如何通过网络套接字可移植地发送 C 结构?
【发布时间】:2015-04-11 20:55:39
【问题描述】:

假设我有一个定义如下的 C 结构:

typedef struct servData {
    char max_word[MAX_WORD];
    char min_word[MAX_WORD];
    int word_count ;
} servSendData ;

其中“MAX_WORD”可以是任何值。 现在,如果我有这个结构的一个实例:

servSendData  myData ;

如果我填充这个实例然后通过网络发送它,考虑到我希望我的服务器和客户端在 64 位系统或 32 位系统上运行,这里是否会有任何可移植性问题系统。

我将按如下方式发送和接收数据:

//server side
strcpy(myData.max_word, "some large word") ;
strcpy(myData.min_word, "small") ;
myData.word_count=100 ;
send(sockFd, (char*)&myData, sizeof(myData);

//client side
recv(sockFd, (char*)&myData, sizeof(myData);
printf("large word is %s\n", myData.max_word) ;
printf("small word is %s\n", myData.min_word) ;
printf("total words is %d\n", myData.word_count) ;

【问题讨论】:

标签: c portability


【解决方案1】:

如果您不是在编写嵌入式软件,那么在没有正确序列化的情况下在应用程序之间发送数据并不是一个好主意。

使用raw socket也是如此,不是很方便,感觉有点像“重新发明轮子”。

许多图书馆都可以为您提供帮助!当然,您不必使用它们,但阅读它们的文档并了解它们的工作原理将帮助您做出更好的选择。您还没有计划的事情可以开箱即用(例如,当您想要更新系统并且消息格式发生变化时会发生什么?)

对于序列化,请阅读这些通用格式:

  • 人类可读:JSON、XML、YAML 等...
  • 二进制:ProtobufTPLAvro、BSON、MessagePack 等等

对于套接字抽象,请查阅

  • 提升 ASIO
  • ZeroMQ
  • 纳米
  • 许多其他人

【讨论】:

    【解决方案2】:

    正如其他依赖者所说,在具有不同编译器/字长/和字节序结构的不同机器之间复制 C 结构存在实际问题。解决此问题的一种常见方法是将数据转换为与机器无关的格式,通过网络传输,然后将其转换回接收器上的结构。这是一个非常普遍的要求,以至于已经存在多种技术来做到这一点 - 我最初想到的两个是 gsoaprpcgen,尽管可能还有许多其他选项。

    我主要使用 gsoap,在您完成最初的学习曲线后,您可以开发强大的解决方案,可以很好地扩展(使用多个线程)并为您处理网络和数据转换。

    如果您不想走这条路,那么最安全的方法是编写将数据转换为标准字符串格式/从标准字符串格式转换的例程(如果您遇到 Unicode 问题,您将也需要考虑到这一点),然后通过网络发送。

    【讨论】:

    • 是的@Jackson,关于 recv() 函数,你是对的。在实际代码中,我确保在开始读取各个字段之前接收到全部数据。
    【解决方案3】:

    是的,肯定会有可移植性问题。

    即使在同一平台上的不同编译器之间,结构成员的对齐方式也可能不同,更不用说不同的平台了。这一切都假设sizeof(int) 在所有这些中都是相同的(尽管被授予,它通常是 --- 但你真的想依靠“通常”并希望最好吗?) .

    即使MAX_WORD 在两台计算机上都是相同的,这仍然成立(我假设他们从这里开始;如果不是,那么你在这里遇到了麻烦)。

    您需要做的是分别发送(和接收)每个字段。 sizeof(int) 和字节序也有问题,所以我添加了对htonl() 的调用以从系统字节顺序转换为网络字节顺序(反函数为ntohl())。他们都返回uint32_t,它有一个固定的、已知的大小。

    send(sockFd, myData.max_word, sizeof(myData.max_word)); // or just MAX_WORD
    send(sockFd, myData.min_word, sizeof(myData.min_word));
    uint32_t count = htonl(myData.word_count); // convert to network byte order
    send(sockFd, &count, sizeof(count));
    
    // error handling!
    if((ret = recv(sockFd, myData.max_word, sizeof(myData.max_word))) != sizeof(myData.max_word))
    {
        // handle error or read more data
    }
    ... // and so on
    // remember to convert back from network byte order on recv!
    // also keep in mind the third field is now `uint32_t`, and not `int` in the stream
    

    【讨论】:

    • 注意:您的if 条件是错误的。即使是真的,也不会是错误的。将使用-1 指示错误。
    • @black:好点子,虽然我只是想把大体的想法传达给大家。我会稍微修一下。
    【解决方案4】:

    您可以使用structure packing。对于大多数 C 编译器,您可以强制执行特定的结构对齐。它有时用于您需要的用途 - 通过网络传输 struct

    请注意,这仍然会留下字节顺序问题,因此这不是通用解决方案。

    【讨论】:

    • 它也不是标准/可移植的 C(或 C+POSIX,因为我们在谈论套接字),而且sizeof(int) 仍然存在问题。使用打包通常还会使代码因软件未对齐读取而膨胀(至少在非 x86 上,因为绝大多数其他架构根本不支持未对齐读取/写入;在 x86 中,它只会使此类读取变慢)。
    • @TimČas:好吧,我并不是说这是要走的路,只是这样可以避免一些兼容性问题。此外,sizeof(int) 对 OP 来说可能不是问题,因为他说他的设置中的客户端和服务器要么都是 x86,要么都是 x64。
    • 嗯,从技术上讲,他说一些 64 位或32 位系统,但是是的,16 位ints 可能没有。 也就是说,他稍后可能会决定在结构中添加long 或类似名称,然后解决问题;我试图更笼统地解决这个问题。
    【解决方案5】:

    您必须注意字节序。

    您应该使用 hton() 或 ntoh() 函数在小端和大端之间进行转换。

    【讨论】:

    • 我非常不同意 --- 字节序是他这里最少的问题。这是一个问题,但sizeof(int) 和结构填充(由于int 的不同对齐方式)是这里更大的问题。
    • 好的,是的,我明白了,x86 上的 int 比 x64 机器上的 int 小。他应该使用 uint32_t 之类的类型,而不是 int。
    • @TimČas 你怎么会“强烈反对”一个属于你自己的真正子集的答案?可移植性问题比字节序更多,您的答案非常准确地涵盖了它们,但这个答案所说的一切也是正确的。
    • @Lui:嗯,通常不在 x86 和 x64 上(但 long 通常是)。但它可以,这取决于编译器。这是相关的:viva64.com/en/t/0012 --- 一个系统可能是 ILP64(64 位 int),另一个 LLP64(32 位 int)和另一个(认为嵌入式)I16LP32(16 位 int )。
    • @5gon12eder:因为他专注于所有问题中最少的问题,而忽略了所有其他更大的问题。
    猜你喜欢
    • 2012-01-20
    • 2016-06-06
    • 1970-01-01
    • 2016-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-24
    • 2010-11-22
    相关资源
    最近更新 更多