Search code examples
c++tcp

How to correctly convert stream of various (known) length packets that are also different data types?


Problem:

I'm writing a C++ program where I want to read a stream of data from a TCP/IP socket. The data consists of several packets of various lengths and data types, however, it is all received in hex format. The lengths of the packets and their data types can be seen in this image: table. I want to convert the received hex formatted data back to the packets' respective original data types.

Background and what I've tried:

I have found a rather easy way to do this in Python:
- Receive each packet according to their known length
- Encode them as hex
- unpack them according to their data type

Here's a sample:

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

I don't care a lot about these next packets, so I'll jump to the relevant ones (from packet_12 and onwards). I'm going to split each 48 byte packet into smaller 8 byte size packets, since the 48 bytes is actually 6 separate values as detailed in the picture above...

 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.....

This produces the message length (which should always be 1108), the server's time and the X, Y position of the robot's tool.


The C++ attempt:

When attempting this in C++ I'm getting a bit confused with the data types, byte ordering, etc. I have managed to produce the first packet correctly (printing 1108 as I expect). However, the next packet does not match the output from the Python implementation. Here's what I have:

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; 

If I instead try declaring my buffer2 as such: unsigned long long int buffer2
I get something closer to what I want, however it is of course not a double like I expect. And casting buffer2 to a double simply results in a 0.

Expected result

I expect the time to be something like 1379408.432, instead of an integer. The X, Y, and Z coordinates from packet_12 also should be doubles.


Solution

  • The ntohl function expects a 32 bit integer and changes the value so effectively the bytes are swapped, assuming host byte order is little endian. It will not work on a value of type double.

    You need to read the value into a char array of size 8, reverse the bytes, then use memcpy to copy the bytes into a 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));
    

    Note that this is dependent on both the sender and the receiver having the same representation for a double.