【问题标题】:Sending struct over TCP (C-programming)通过 TCP 发送结构(C 编程)
【发布时间】:2010-12-16 15:06:25
【问题描述】:

我有一个客户端和服务器程序,我想从客户端发送整个结构,然后在服务器上输出结构成员“ID”。

我已经完成了所有的连接等,并且已经设法通过以下方式发送一个字符串:

send(socket, string, string_size, 0);

那么,是否可以通过 send() 发送一个结构体而不是一个字符串?我可以将服务器上的缓冲区替换为相同类型的空结构并继续吗?

【问题讨论】:

  • 感谢大家提供的所有信息丰富的答案!

标签: c macos tcp struct send


【解决方案1】:

是的,相同类型的结构都具有相同的大小。如果你的指针投射正确,你就可以开始了。

【讨论】:

  • 好!您能否提供一个小而简单的例子来说明如何解决这个问题?
  • 真的不这么认为...相同类型的结构的大小不同,因为它取决于应用于结构成员的填充。此外,您在架构 (32/64) 和字节序方面存在问题。
  • Stefano 和其他人当然是对的:这只有在您使用兼容的 C 架构时才有效。您已经确认是这样,所以您可以按如下方式发送(未经测试,抱歉!) send(socket, ((char *) &yourStruct, sizeof(yourStruct), 0)。我注意到这看起来很像 rasher 的解决方案但我认为获取结构的地址更正确。(&)。
  • read(socket, struct_buffer, sizeof(struct_buffer); 的结果在我测试时返回-1。我没有正确接收到这个吗?
  • 我知道,上述评论中缺少括号。不在源代码中..
【解决方案2】:

客户端和服务器机器“相同”吗?只有在每一端的 C 编译器在内存中布局完全相同的结构时,您所提议的内容才会起作用。有很多原因可能不是这种情况。例如,客户端和服务器机器可能具有不同的架构,那么它们在内存中表示数字的方式(大端、小端)可能会有所不同。即使客户端机器和服务器机器具有相同的架构,两个不同的 C 编译器也可能对它们在内存中布局结构的方式有不同的策略(例如,在字段之间填充以对齐字边界上的整数)。即使是相同的编译器带有不同的标志也可能会给出不同的结果。

务实地说,我猜您的客户端和服务器是同一种机器,因此您提出的建议将起作用,但是您需要注意,作为一般规则,它不会,这就是为什么标准如CORBA 是发明出来的,或者为什么人们使用 XML 等通用表示。

【讨论】:

  • 是的,客户端和服务器机器是一样的,所以这不是问题。感谢您的提示:)
【解决方案3】:

如果客户端和服务器以完全相同的方式布局结构,则可以,这意味着字段大小相同,填充相同。例如,如果您的结构中有一个long,则在一台机器上可能是 32 位,而在另一台机器上可能是 64 位,在这种情况下,将无法正确接收结构。

在你的情况下,如果客户端和服务器总是在非常相似的 C 实现上(例如,如果这只是你用来学习一些基本概念的代码,或者如果由于某些其他原因你知道你的代码只需要在您当前版本的 OSX 上运行),那么您可能可以摆脱它。请记住,您的代码不一定能在其他平台上正常工作,而且在它适合在大多数实际情况下使用之前还有很多工作要做。

对于大多数客户端-服务器应用程序,这意味着答案是您通常无法做到这一点。您实际上所做的是根据发送的字节数、顺序、含义等来定义消息。然后在每一端,您都执行特定于平台的操作,以确保您使用的结构具有完全所需的布局。即便如此,如果您要发送 struct little-endian 的整数成员,您可能必须进行一些字节交换,然后您希望您的代码在 big-endian 机器上运行。存在 XML、json 和 Google 的协议缓冲区等数据交换格式,因此您不必做这些繁琐的事情。

[编辑:当然还要记住,某些结构成员永远不能通过网络发送。例如,如果您的结构中有一个指针,那么该地址指的是发送机器上的内存,而在接收端是无用的。抱歉,如果这对您来说已经很明显了,但对于刚开始使用 C 的每个人来说肯定不是很明显。

【讨论】:

  • 从技术上讲,假设他的连接协议正常工作,结构将始终被正确接收。问题不在于接收,而在于解释,或者您可以将其称为访问。
  • 是的。我会说,如果您实际上并未将所有字节读入应用程序空间(因为阅读器上的sizeof(thestruct) 小于编写器),那么您还没有收到该消息。另外,我认为从技术上讲,有时在结构上复制任意字节是未定义的行为——您可以触发成员中的陷阱位。所以在那种情况下也没有收到消息。但你是对的,你会收到(一些)消息的字节,只是不理解消息本身。
【解决方案4】:

嗯...如果你做得好,通过网络发送结构有点困难。

Carl 是对的 - 你可以通过网络发送一个结构体:

send(socket, (char*)my_struct, sizeof(my_struct), 0);

但事情是这样的:

  • sizeof(my_struct) 可能会在客户端和服务器之间发生变化。编译器通常会进行一些填充和对齐,因此除非您明确定义对齐方式(可能使用#pragma pack()),否则该大小可能会有所不同。
  • 另一个问题往往是字节顺序。有些机器是大端的,有些机器是小端的,所以字节的排列可能不同。实际上,除非您的服务器或客户端在非英特尔硬件上运行(可能并非如此),否则这个问题在理论上比在实践中更存在。

所以人们经常提出的解决方案是有一个序列化结构的例程。也就是说,它一次向 struct 发送一个数据成员,确保客户端和服务器只 send() 和 recv() 您在程序中编码的确切指定字节数。

【讨论】:

  • 这不是很重要,但您可能应该将结构转换为 void*,而不是 char *,因为这就是 send() 的原型。
  • @rasher:虽然您提到了打包结构和序列化,但重要的是要指出,如果您发送大量数据,序列化会降低性能。每次调用 send 都会在用户空间和内核空间之间进行相当昂贵的上下文切换。打包数据确实是首选方法。
【解决方案5】:

你可以,但你需要注意两件重要的事情。

  1. 两端的程序必须与ABI 兼容。如果两端都运行在相同的处理器架构、相同的操作系统、使用相同的编译器和编译器标志编译,它们通常是这样。
  2. TCP 是一个流。您必须确保发送整个结构。查看 send() 调用的文档 - 它返回发送的字节数 - 这可能比您告诉它的要少。接收端也一样。仅仅因为您通过 1 个 send 调用发送了该结构,并不意味着您将通过 1 个 recv 调用收到它。需要几次 recv 调用才能获取所有部分。

【讨论】:

    【解决方案6】:

    一般来说,这样做是个坏主意,即使您的客户端和服务器最终以相同的方式在内存中布局结构。

    即使您不打算来回传递更复杂的数据结构(可能涉及指针),我建议您在通过网络发送数据之前对数据进行序列化。

    【讨论】:

    • 序列化数据是最糟糕的解决方案,因为它会导致用户和内核空间之间昂贵的不必要的上下文切换以将数据复制到内核缓冲区,从而降低系统性能。要么安排struct 中的成员以保证没有填充,要么使用pack pragma。
    • Robert S. Barnes:我什至不知道如何开始回答这种荒谬的过度概括。让我们暂时忽略与原始问题的相关性,并考虑用户空间和内核之间的副本必须有多少数据才能产生可​​衡量的差异。另一方面,我们根本不去想它。
    • 也许我应该改写:序列化应该是你最后的手段,因为每次调用发送都会导致极其昂贵的上下文切换。例如:linfo.org/context_switch.html“上下文切换通常是计算密集型的。也就是说,它需要相当多的处理器时间,对于每秒数十或数百个切换中的每一个,它可能在纳秒的数量级。因此,上下文切换代表了一个实质性的CPU 时间方面的系统成本,实际上可能是操作系统上最昂贵的操作。”查找“上下文切换成本”
    • Robert S. Barnes:这是我可以接受的观点。我仍然看不出序列化与上下文切换所施加的开销有什么关系。 (上下文切换的含义对我来说很清楚。)我看到的最重要的性能问题是,对于(反)序列化,数据必须在内存中复制。这仅在高吞吐量情况下相关。我认为为那些没有任何实际需要的人进行优化是过早的优化(因此是万恶之源)。
    【解决方案7】:

    将数据作为文本文件发送,接收后解码。如果您希望发送数据,这是最好的方法!

    【讨论】:

      猜你喜欢
      • 2012-08-10
      • 2010-12-16
      • 2016-01-11
      • 1970-01-01
      • 2018-06-26
      • 2017-04-24
      • 1970-01-01
      • 1970-01-01
      • 2014-09-02
      相关资源
      最近更新 更多