C ++ serialization - using reinterpret_cast from char * to struct

I am sharing a named structure with struct update_packet

other servers (identical or similar system) running the same program over a socket UDP

using sendto(..)

and recvfrom()

.

update_packet

must be in the general message format, which means that its fields have a predefined fixed size, and the structure size is the sum of the fields.

struct node {
    uint32_t IP;
    uint16_t port;
    int16_t nil;
    uint16_t server_id;
    uint16_t cost;
};

struct update_packet {
    uint16_t num_update_fields;
    uint16_t port;
    uint32_t IP;

    struct node * nodes;

    update_packet() :
        num_update_fields(num_nodes), IP(myIP), port(myport)
        {//fill in nodes array};
};

      

( update_packet

contains an array of pointers struct node

)

I used reinterpret_cast

to send an instance update packet

over UDP and the following compiles and sends to the correct destination.

int update_packet_size = sizeof(up);
sendto(s, reinterpret_cast<const char*>(&up), update_packet_size, 0,
       (struct sockaddr *)&dest_addr, sizeof(dest_addr));

      

However, when I receive it and try to decode it with

struct update_packet update_msg =
    reinterpret_cast<struct update_packet>(recved_msg);

      

I am getting the error

In function β€˜int main(int, char**)’:
error: invalid cast from type β€˜char*’ to type β€˜update_packet’
           struct update_packet update_msg = 
           reinterpret_cast<struct update_packet>(recved_msg);

      

Why is this error occurring and how can I fix it?

Also, is this the correct way to exchange data in an instance struct

over sockets? If not, what should I do? I need an pack()

ing function like in http://beej.us/guide/bgnet/examples/pack2.c ?

+3


source to share


3 answers


General Provisions

Casting has been correctly answered in other questions.

However, you should never rely on casting a pointer to send / receive a struct across the network for many reasons, including:

  • Packing: the compiler can align structural variables and insert padding bytes. It is compiler dependent, so your code will not be portable. If two communication machines are running your program compiled with different compilers, it probably won't work.
  • Endianness: For the same reason, the byte order when sending a multibyte number (like an int) can be different between the two machines.

This will lead to code that can run multiple times, but it will cause tons of problems in a few years if someone changes the compiler, platform, etc. How it's done for an educational project you must try to do it right ...

For this reason, converting data from a structure to a char array for sending over the network or writing to a file must be done carefully, variable by variable and, if possible, account for it. This process is called "serialization".

Detailed description of serialization

Serialization means that you convert the data structure to a byte array that can be sent over the network.

The serialized format is not necessarily binary: text or xml are possible options. If the amount of data is small, text might be a better solution and you can only rely on STL with string streams (std :: istringstream and std :: ostringstream)

There are several good libraries for binary serialization, such as Boost :: serialization or QDataStream in Qt. You can also do it yourself, take a look at SO for "C ++ serialization"

Simple text serialization using STL

In your case, you can simply serialize the text string using something like:

std::ostringstream oss;

oss << up.port;
oss << up.IP;
oss << up.num_update_fields;
for(unsigned int i=0;i<up.num_update_fields;i++)
{
    oss << up.nodes[i].IP;
    oss << up.nodes[i].port;
    oss << up.nodes[i].nil;
    oss << up.nodes[i].server_id;
    oss << up.nodes[i].cost;
}

std::string str = oss.str();

char * data_to_send = str.data();
unsigned int num_bytes_to_send = str.size();

      

And to deserialize the received data:

std::string str(data_received, num_bytes_received);
std::istringstream(str);


update_packet up;
iss >> up.port;
iss >> up.IP;
iss >> up.num_update_fields;
//maximum number of nodes should be checked here before doing memory allocation!
up.nodes = (nodes*)malloc(sizeof(node)*up.num_update_fields);
for(unsigned int i=0;i<up.num_update_fields;i++)
{
    iss >> up.nodes[i].IP;
    iss >> up.nodes[i].port;
    iss >> up.nodes[i].nil;
    iss >> up.nodes[i].server_id;
    iss >> up.nodes[i].cost;
}

      



It will be 100% portable and safe. You can check the validity of the data by checking the iss error flags.

You can also, for safety:

  • Use std :: vector instead of a node pointer. This will prevent memory leaks and other problems.
  • Check the number of nodes immediately after iss >> up.num_update_fields;

    , if it is too large, just stop decoding before allocating a huge buffer that will crash your program and possibly your system. Network attacks are based on holes like this: you can crash the server by causing it to allocate a buffer 100 times more than its RAM if this type of check is not performed.
  • If your networking API has std :: iostream interface, you can directly use the <and β†’ operators from it, without using intermediate string and string streams
  • You might think that using space separated text is a waste of bandwidth. Think about this only if your node count is large and bandwidth usage becomes unimportant and critical. In this case, you need to do the serialization to binary. But don't do this if the text solution works great (beware of premature optimization!)

Simple binary serialization (not specified in bytes / directions):

Replace:

oss.write << up.port;

      

By:

oss.write((const char *)&up.port, sizeof(up.port));

      

Byte order

But your project requires Big-Endian. If you are on PC (x86) you need to invert the bytes in each field.

1) First option: manually

const char * ptr = &up.port;
unsigned int s = sizeof(up.port);
for(unsigned int i=0; i<s; i++)
    oss.put(ptr[s-1-i]);

      

Final code: define endianness (it's not hard to do - find it on SO) and adapt the serialization code.

2) Second option: use a library like boost or Qt

In these libraries, you can choose the finiteness of the output. They then automatically detect the end of the platform and complete the task automatically.

+5


source


You cannot override a pointer to a structure, but you can override a pointer to a pointer to a structure.

Change

struct update_packet update_msg = 
       reinterpret_cast<struct update_packet>(recved_msg);

      



to

update_packet * update_msg = 
       reinterpret_cast<update_packet *>(recved_msg);

      

And yes, you do, at least, pack()

because the submit-side compiler can add annotations in different ways. However, it is not 100% secure. You also take into account that the sending and receiving machine are different in the end. I would suggest that you learn the correct serialization mechanisms.

+2


source


You can also use:

struct update_packet update_msg;

memcpy(&update_msg, recved_msg, size-of-message);

      

However, you must make sure that size-of-message

- this is exactly what you are looking for.

+2


source







All Articles