如何正确转换也是不同数据类型的各种(已知)长度数据包的流?

时间:2019-04-03 14:18:06

标签: c++ tcp

问题:

我正在编写一个C ++程序,该程序要从TCP / IP套接字读取数据流。数据由不同长度和数据类型的几个数据包组成,但是全部以十六进制格式接收。可以在此图中看到数据包的长度及其数据类型: table。 我想将接收到的十六进制格式的数据转换回数据包各自的原始数据类型。

背景以及我尝试过的方法:

我在Python中找到了一种相当简单的方法:
-根据已知长度接收每个数据包
-将它们编码为十六进制
-根据其数据类型将其解压缩

这里是一个示例:

import socket
import struct
HOST = "192.168.0.25" # The remote host
PORT_30003 = 30003

while (True):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((HOST, PORT_30003))
        print ""
        packet_1 = s.recv(4)
        packet_2 = s.recv(8)
        packet_3 = s.recv(48)
        packet_4 = s.recv(48)
        packet_5 = s.recv(48)
        packet_6 = s.recv(48)
        packet_7 = s.recv(48) 
        packet_8 = s.recv(48)
        packet_9 = s.recv(48)
        packet_10 = s.recv(48)
        packet_11 = s.recv(48)

        packet_1 = packet_1.encode("hex")
        messageSize = struct.unpack('!i', packet_1.decode('hex'))[0]
        print "messageSize = ", messageSize

        packet_2 = packet_2.encode("hex")
        Time = struct.unpack('!d', packet_2.decode('hex'))[0]
        print "Time = ", Time

我不太关心这些下一个数据包,因此我将跳转到相关的数据包(从packet_12开始)。我将把每个48字节的数据包拆分为较小的8字节大小的数据包,因为48个字节实际上是6个单独的值,如上图所示...

 packet_12x = s.recv(8)
 packet_12x = packet_12x.encode("hex")
 x = struct.unpack('!d', packet_12x.decode('hex'))[0]
 print "X = ", x * 1000

 packet_12y = s.recv(8)
 packet_12y = packet_12y.encode("hex")
 y = struct.unpack('!d', packet_12y.decode('hex'))[0]
 print "Y = ", y * 1000

 # And so on.....

这将产生消息长度(应始终为1108),服务器时间和机器人工具的X,Y位置。


C ++尝试:

在C ++中尝试此操作时,我对数据类型,字节顺序等感到有些困惑。我设法正确产生了第一个数据包(按我的预期打印1108)。但是,下一个数据包与Python实现的输出不匹配。这是我所拥有的:

connectTcpClient("192.168.0.25");

// This works as expected
int buffer1; 
recv(sock, &buffer1, 4, 0);
buffer1 = ntohl(buffer1);

// Prints "messageSize = 1108"
std::cout << "messageSize = " << buffer1 << std::endl; 

// This does not produce the same result as the Python code
// When trying with a unsigned long long int instead of double 
double buffer2; 
recv(sock, &buffer2, 8, 0);
buffer2 = ntohl(buffer2);

// This prints "Time: 0"
std::cout << "Time: " << buffer2 << std::endl; 

如果我改为这样声明我的buffer2unsigned long long int buffer2
我得到的东西接近我想要的东西,但是当然不是我期望的两倍。将buffer2强制转换为double只会产生0。

预期结果

我希望时间大约是1379408.432,而不是整数。 packet_12中的X,Y和Z坐标也应为双精度。

1 个答案:

答案 0 :(得分:1)

ntohl函数需要一个32位整数并更改该值,以有效地交换字节(假定主机字节顺序为小端)。不适用于类型double的值。

您需要将值读入大小为8的char数组中,反转字节,然后使用memcpy将字节复制到double中。

int i;
unsigned char dbuff[sizeof(double)];
recv(sock, dbuff, sizeof(dbuff), 0);

if (ntohl(0x12345678) == 0x78563412) {
    for (i=0; i<sizeof(double)/2; i++) {
        unsigned char tmp = dbuff[i];
        dbuff[i] = dbuff[sizeof(double)-1-i];
        dbuff[sizeof(double)-1-i] = tmp;
    }
}

double val;
memcpy(&val, dbuff, sizeof(double));

请注意,这取决于double的发送方和接收方具有相同的表示形式。