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 ?
source to share
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.
source to share
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.
source to share