C++ Winsock 结构发送/接收

Posted

技术标签:

【中文标题】C++ Winsock 结构发送/接收【英文标题】:C++ Winsock struct send/recv 【发布时间】:2015-02-23 07:42:44 【问题描述】:

我正在尝试通过 winsock UDP 套接字从客户端向服务器发送一个结构,但我没有收到已发送的相同字节,或者至少没有正确重组该结构。

struct Header

  uint16_t sender;
  uint16_t number;
  uint16_t packetSize;
  uint16_t type; 
;

发送结构:

Header header;
header.sender = 1;
header.number = 2;
header.packetSize = 3;
header.type = 4;

char buffer[sizeof(Header)];
memcpy(buffer, &header, sizeof(Header));

 int bytesSent = sendto(sendSocket_, data, sizeof(Header), 0, (const sockaddr*)&sendSocketAddr_, sizeof(sendSocketAddr_));
  if (bytesSent > 0)
  
    cout << bytesSent << endl;
  

接收结构:

char *data = new char[sizeof(Header)];
  int bytesRecv = recv(listenSocket_, data, sizeof(Header), 0);
  if (bytesRecv > 0)
  
    cout << bytesRecv << endl;
    Header header;
    memcpy(&header, data, sizeof(data));
    cout << header.sender << ": " << header.number << ": " << header.packetSize << ": " << header.type << endl;

收到的字节数与发送的相同,但是一旦复制回服务器端的新结构中,只有前两个值是正确的,其余的则不是。

在这种情况下,结构在接收时包含 1、2、52428、52428。

我在发送/接收过程中遗漏了什么,还是与在另一端创建结构有关?

【问题讨论】:

客户端和服务器是否使用相同的编译器使用相同的选项进行编译?如果不是,结构中字段的对齐方式可能不同。 是的,客户端和服务器都使用相同的编译器使用相同的选项进行编译。 我不确定是否为缓冲区数组分配了正确的大小。您应该在 memcpy 中有sizeof(header)(变量的大小,而不是类型)。在发送之前打印出缓冲区的内容,以确保您发送的内容是正确的。 不要这样做。不要将结构用作网络协议。使用网络协议作为网络协议。以八位字节定义有线格式,并为自己编写一个库来发送和接收它。如果你使用结构,你至少会引入六个依赖项。 您是否尝试检查bytesSent 的值是否等于sizeof(Header) 【参考方案1】:

首先,通过网络发送原始结构是高度不可移植的:

发送方和接收方之间可能存在字节顺序差异 您可以发现发送方和接收方之间的对齐差异

除非在您可以确定发送方和接收方共享相同架构、操作系统和编译器的特殊情况下,永远不要这样做

通常的方法是使用可移植的格式,ASCII(稳健但通常不是最佳)或二进制,但您必须定义一个明确的表示。例如,当您使用 4 uint16_t 时:

包装:

char buffer[8];
buffer[0] = header.sender >> 8; /* or (header.sender >> 8) & 0xFF if longer ...*/
buffer[1] = header.sender & 0xFF;
buffer[2] = header.number >> 8;
...

开箱:

header.sender = (buffer[0] << 8) | buffer[1];
header.number = (buffer[2] << 8) | buffer[3];
...

但是如果你遇到不同架构的问题,你就不会得到前两个值。您当前的问题要简单得多:在接收代码时:

char *data = new char[sizeof(Header)];
....
    memcpy(&header, data, sizeof(data));

sizeof(data)sizeof(char *),在 32 位机器中是 4 个字节,而 sizeof(Header) 是 4 * 2 = 8 个字节。

你应该有:

    memcpy(&header, data, sizeof(Header));

顺便说一句,您可以避免 memcpy 部分。

发件人部分:

int bytesSent = sendto(sendSocket_, &header, sizeof(Header), 0, (const sockaddr*)&sendSocketAddr_, sizeof(sendSocketAddr_));

有效,因为sendto 的第二个参数是const void *,并且自动转换为void *

接收者:

int bytesRecv = recv(listenSocket_, &header, sizeof(Header), 0);

因为recv 的第二个参数实际上是void *

【讨论】:

您还可以有填充差异或数据类型大小差异。所有这些都可能因硬件、操作系统、编译器、编译器版本、编译器标志、编译器选项、周围的#pragmas 等而异……更不用说源代码本身了。使用此技术的唯一安全方法是确保两个对等点都是从相同的目标文件构建的。【参考方案2】:

您可以使用:#pragma pack(show) 将对齐打印为警告并确保它是相同的。

如果有区别,请像这样使用#pragma pack(n)

#pragma pack(push)
#pragma pack(1)
struct Header
  ...
;
#pragma pack(pop)

您不必memcpy 结构来缓冲。您可以直接使用您的标头对象。

【讨论】:

以上是关于C++ Winsock 结构发送/接收的主要内容,如果未能解决你的问题,请参考以下文章

将 JavaScript WebSocket 连接到 C winsock

Winsock 仅在程序关闭时发送数据

Server->Client 之间的 C++ Winsock 通信,反之亦然

通过 SOCKS5 c++ 发送和接收

winsock 服务器同时发送和接收

Winsock2,客户端-服务器通信——轮流发送/接收