4
votes

I'm working with unix sockets, and I can send() and recv() data when my buffer is of type char (ie. sending and receiving strings). I used Beej's guide to sockets, and the examples used were for send'ing/recv'ing strings.

Now I want to send/recv data of different types in one message.
For example, say in one message I want to send an integer, a string, a double and a float. How should I go about doing this? More specifically, of what type should my message 'buffer' be?

The prototypes for send and recv:

int recv (int socket, void *buffer, size_t size, int flags)
int send (int socket, void *buffer, size_t size, int flags)

I don't have too much experience with C/C++ and pointers, so this is probably a noob question.

If anyone can guide me in the right direction, I'd really appreciate it. Thanks

5

5 Answers

2
votes

You need to serialize objects on the sender side and deserialize on the receiver side. There is a number of libraries for that in C++, for example Boost Serialization or if you feel like reinventing the wheel you can always roll your own.

2
votes

Unless you are planning to send vast amounts of data (many kilobytes) and often (several packets per second), I would suggest that you translate your data to strings (aka "serialize the data") and pass it that way. It has several benefits:

  1. It's portable - works no matter what size an int or float or double is - or what the padding between fields are in the structure.
  2. It's easy to debug (you can just look at the data and say whether it is right or wrong)
  3. It doesn't matter what byte-order the sending/receiving machines are.

Sending binary data, on the other hand is complex, because you need to worry about the individual sizes of data fields and their internal representation (byte order, how a double is represnted in binary, padding of data fields within structures, can't pass pointers, etc, etc). The only benefit is that binary data is a little more compact. But that only matters if you have many kilobytes and/or send lots of packets every second.

2
votes

The best way would be to encapsulate the information that you want to pass in a structure. And then pass the address and length of the structure. Note that buffer is a void* so this allows you to pass the address of the structure containing your information.

Here is a small snippet of the struct I had used for a secure copy like tool,

struct message
{
     size_t total_len;
     size_t filename_len;
     char *filename;
     size_t text_len;
     char *text;
     char *iv;
     char *salt;
     unsigned char *hmac;
};

Then in your send_message() serialize the message and then write to the socket

bool send_message(const struct message *msg, const char *ip, const char *port)
{
    ...
   connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
   // Serialize to file
   serialize(msg, "temp");

   // Transfer the file over the network
   fp = fopen("temp", "rb");
   while((n = fread(buffer, 1, MAX_BUF_LEN, fp)) != 0)
   {
       write(sockfd, buffer, n);
   }

   fclose(fp);
   remove("temp");

   close(sockfd);
}

As suggested by @vitaut, you can use a library to serialize, I had implemented it as a learning experience.

2
votes

Well, to answer the question as asked ("How do I send structs?"), you just send a pointer to the structure.

struct foo
{
  int a;
  int b;
};

struct foo s;
s.a = 10;
s.b = 20;
send(socket,&s,sizeof(s),0);

It really is that easy. Now, the other side has to be the same (that is, the structure as laid out in memory on system A must match the structure as laid out on system B). A better way is to use more specific types and some functions to order the data properly:

#ifndef __GNUC__
#  define __attribute__(x)
#endif

#ifdef __SunOS
#  pragma pack(1)
#endif

struct foo
{
  uint32_t a;
  uint32_t b;
} __attribute__((packed));

#ifdef __SunOS
#  pragma pack()
#endif

struct foo s

/* to send */

s.a = htonl(10);
s.b = htonl(20);
send(socket,&s,sizeof(s),0);

/* to receive */
recv(socket,&s,sizeof(s),0);
s.a = ntohl(s.a);
s.b = ntohl(s.b);

You can see it starts to get system specific rather quick when you want to "portably" send binary data across a network, which is why people are saying to convert to text, or quite possibly use a serialization library that's already written.

0
votes

That depends on how portable this must be.

The easiest way is to convert everything to ASCII separated by comma or semicolon and parse it again on the receiving side.

You can also convert it to network byte order and send it as binary.