从 tcp 读取浮点数

Read floats from tcp

我正在尝试读取从 Python tcp 流服务器中的打包结构传递的 big-endian 8 32 位浮点数。它似乎接近工作,但前几个值有奇数的偏差,后面的值似乎有少量偏差,或者只是不精确。

例如,这些是客户端解释的值:

Val[0] -1926.34
Val[1] -1936.86
Val[2] -1901.15
Val[3] -1935.93
Val[4] -148932
Val[5] -145905
Val[6] -41580.8
Val[7] -134330

这里是(接近但现在相当)它们应该是的值。这不是真实的,因为很难在服务器和客户端上捕获完全相同的数据包。

Val[0] -7737.77159902711
Val[1] -7746.444075875769
Val[2] -7638.46279841218
Val[3] -7776.037785534595
Val[4] -148935.79768369172
Val[5] -145903.3365134402
Val[6] -41594.9200504923
Val[7] -134328.9103304041

这是我的代码:

int size = 32;
char buffer[size];
float vals[8];
int count = 0;
int t;

// Receive a reply from the server
if (recv(sock, buffer, size, 0) < 0) {std::cout << "Receive failed..." << std::endl;}

for (int i = 0; count < 8; i += 4, count++) {
    t =
            (buffer[i+3])       +
            (buffer[i+2] << 8)  +
            (buffer[i+1] << 16) +
            (buffer[i]   << 24);

    vals[count] = *reinterpret_cast<float*>(&t);
}

Python 服务器发送数据包:

packer = struct.Struct('>%sf' % 8)
packed_data = packer.pack(*values)
sock.send(packed_data)

我感觉这可能是关于 int 和 float 之间转换的问题,但我似乎无法弄清楚。非常感谢任何帮助。

  1. buffer 正在使用 char,它可能已在您的系统上签名。

    如果 buffer[0]-1,那么它会转换为 -1 作为 int(不再是 char)。这在将所有这些字节加在一起时会导致问题(因为 -1 作为 char 可能是 0xff,而作为 int 可能是 0xffffffff)。换句话说,如果缓冲区包含具有负值的字节,由于整数提升,这将搞砸 "merging" 字节。

  2. 你在那里的重新解释打破了严格的别名规则。这很可能适用于您可能使用的任何系统,但严格来说是未定义的行为。

您可以将其替换为将字节直接分配给 float 的代码,如下所示:

int size = 32;
char buffer[size];
float vals[8];
int count = 0;
int t;

// Receive a reply from the server
if (recv(sock, buffer, size, 0) < 0) {std::cout << "Receive failed..." << std::endl;}

for (int i = 0; count < 8; i += 4, count++) {
    char* ptr = (char*)(vals + count);
    // switch endianness as needed (though unlikely)
    // (this is effectively equivalent to std::memcpy(vals, buffer, size))
    ptr[0] = buffer[0];
    ptr[1] = buffer[1];
    ptr[2] = buffer[2];
    ptr[3] = buffer[3];
}

更好的是(为了避免不必要的内存复制),如果你知道两台机器使用相同的浮点格式和大小,你可以用 recv:

// read the bytes directly into the floats
recv(sock, vals, size, 0);

您的代码正在使用 chars 进行位操作,这在默认情况下对 char 进行签名的平台(大多数平台)上可能会出现问题。使用 unsigned char 更适合这种用途。

此外,您没有正确实现从套接字读取。 recv 调用不能保证获得您请求的数据量...换句话说,传递的大小只是可以读取的 maximum 数量并且有效接收的数据可能会更少。

从套接字读取时需要进行循环,在收到所有数据或结果为负数或零时退出(负数表示错误,零表示另一端已关闭端点,不再数据来了)。

char buffer[size];
...
t =
        (buffer[i+3])       +
        (buffer[i+2] << 8)  +
        (buffer[i+1] << 16) +
        (buffer[i]   << 24);

这不是字节交换 32 位整数的正确方法。如果 char 被签名了怎么办? (如果您使用 Windows、linux 或 OSX,就是这种情况。)

正确的方法是使用function/macrontohl。虽然这不是 C 或 C++ 标准的一部分,但您会在任何有望在 Internet 上运行的小端系统上找到它。在 Unix 机器上,您需要 #include <arpa/inet.h>。在 Windows,您需要 #include <winsock2.h>.

另一种选择是不要给自己造成这种痛苦。从某种意义上说,这完全是您自己通过在 python 脚本中使用 packer = struct.Struct('>%sf' % 8) 来完成的。您显然知道目标将 运行 在小端机器上,因此将 Struct 构造函数中的 > 更改为“<”。如果您知道 python 脚本将 运行 与您的 C++ 应用程序在同一台机器上,请将 > 更改为 @=