Passing a structure through Sockets in C

2019-01-01 03:40发布

问题:

I am trying to pass whole structure from client to server or vice-versa. Let us assume my structure as follows

struct temp {
  int a;
  char b;
}

I am using sendto and sending the address of the structure variable and receiving it on the other side using the recvfrom function. But I am not able to get the original data sent on the receiving end. In sendto function I am saving the received data into variable of type struct temp.

n = sendto(sock, &pkt, sizeof(struct temp), 0, &server, length);
n = recvfrom(sock, &pkt, sizeof(struct temp), 0, (struct sockaddr *)&from,&fromlen);

Where pkt is the variable of type struct temp.

Eventhough I am receiving 8bytes of data but if I try to print it is simply showing garbage values. Any help for a fix on it ?

NOTE: No third party Libraries have to be used.

EDIT1: I am really new to this serialization concept .. But without doing serialization cant I send a structure via sockets ?

EDIT2: When I try to send a string or an integer variable using the sendto and recvfrom functions I am receiving the data properly at receiver end. Why not in the case of a structure? If I don\'t have to use serializing function then should I send each and every member of the structure individually? This really is not a suitable solution since if there are \'n\' number of members then there are \'n\' number of lines of code added just to send or receive data.

回答1:

This is a very bad idea. Binary data should always be sent in a way that:

  • Handles different endianness
  • Handles different padding
  • Handles differences in the byte-sizes of intrinsic types

Don\'t ever write a whole struct in a binary way, not to a file, not to a socket.

Always write each field separately, and read them the same way.

You need to have functions like

unsigned char * serialize_int(unsigned char *buffer, int value)
{
  /* Write big-endian int value into buffer; assumes 32-bit int and 8-bit char. */
  buffer[0] = value >> 24;
  buffer[1] = value >> 16;
  buffer[2] = value >> 8;
  buffer[3] = value;
  return buffer + 4;
}

unsigned char * serialize_char(unsigned char *buffer, char value)
{
  buffer[0] = value;
  return buffer + 1;
}

unsigned char * serialize_temp(unsigned char *buffer, struct temp *value)
{
  buffer = serialize_int(buffer, value->a);
  buffer = serialize_char(buffer, value->b);
  return buffer;
}

unsigned char * deserialize_int(unsigned char *buffer, int *value);

Or the equivalent, there are of course several ways to set this up with regards to buffer management and so on. Then you need to do the higher-level functions that serialize/deserialize entire structs.

This assumes serializing is done to/from buffers, which means the serialization doesn\'t need to know if the final destination is a file or a socket. It also means you pay some memory overhead, but it\'s generally a good design for performance reasons (you don\'t want to do a write() of each value to the socket).

Once you have the above, here\'s how you could serialize and transmit a structure instance:

int send_temp(int socket, const struct sockaddr *dest, socklen_t dlen,
              const struct temp *temp)
{
  unsigned char buffer[32], *ptr;

  ptr = serialize_temp(buffer, temp);
  return sendto(socket, buffer, ptr - buffer, 0, dest, dlen) == ptr - buffer;
}

A few points to note about the above:

  • The struct to send is first serialized, field by field, into buffer.
  • The serialization routine returns a pointer to the next free byte in the buffer, which we use to compute how many bytes it serialized to
  • Obviously my example serialization routines don\'t protect against buffer overflow.
  • Return value is 1 if the sendto() call succeeded, else it will be 0.


回答2:

Using the \'pragma\' pack option did solved my problem but I am not sure if it has any dependencies ??

#pragma pack(1)   // this helps to pack the struct to 5-bytes
struct packet {
int i;
char j;
};
#pragma pack(0)   // turn packing off

Then the following lines of code worked out fine without any problem

n = sendto(sock,&pkt,sizeof(struct packet),0,&server,length);

n = recvfrom(sock, &pkt, sizeof(struct packet), 0, (struct sockaddr *)&from, &fromlen);


回答3:

If you don\'t want to write the serialisation code yourself, find a proper serialisation framework, and use that.

Maybe Google\'s protocol buffers would be possible?



回答4:

There is no need to write own serialisation routines for short and long integer types - use htons()/htonl() POSIX functions.



回答5:

Serialization is a good idea. You can also use Wireshark to monitor the traffic and understand what is actually passed in the packets.



回答6:

Instead of serialising and depending on 3rd party libraries its easy to come up with a primitive protocol using tag, length and value.

Tag: 32 bit value identifying the field
Length: 32 bit value specifying the length in bytes of the field
Value: the field

Concatenate as required. Use enums for the tags. And use network byte order...

Easy to encode, easy to decode.

Also if you use TCP remember it is a stream of data so if you send e.g. 3 packets you will not necessarily receive 3 packets. They maybe be \"merged\" into a stream depending on nodelay/nagel algorithm amongst other things and you may get them all in one recv... You need to delimit the data for example using RFC1006.

UDP is easier, you\'ll receive a distinct packet for each packet sent, but its a lot less secure.



回答7:

If the format of the data you want to transfer is very simple then converting to and from an ANSI string is simple and portable.