user1493855
user1493855

Reputation: 9

Writing messages of different types through sockets in C

I want to send a message to my Binder class via TCP socket connection in C. I have to pass in a request type (char*), ip address (int), argTypes(int array) etc. through this connection using the write() method. What's the best method to send all of the information in one single message?

Upvotes: 0

Views: 2467

Answers (3)

jxh
jxh

Reputation: 70382

If you want to transmit and receive your data with a single write and single read, then you have to use a datagram socket. Since a datagram socket is connectionless, you cannot use write/read. Instead, you use sendto/recvfrom or sendmsg/recvmsg. Since a datagram socket is unreliable, you will have to implement your own protocol to tolerate out of order data delivery and data loss.

If you don't want to deal with the unreliable nature of a datagram socket, then you want a stream socket. Since a stream socket is connected, transmitted data are guaranteed and are in order. If the data you send always has the same size, then you can mimic a datagram by using blocking mode for your send call and then passing in MSG_WAITALL in the recv call.

#define MY_MSG_SIZE 9876

int my_msg_send (int sock, const void *msg) {
    int r, sent = 0;
    do {
        r = send(sock, (const char *)msg + sent, MY_MSG_SIZE - sent,
                 MSG_NOSIGNAL);
        if (r <= 0) {
            if (r < 0 && errno == EINTR) continue;
            break;
        }
        sent += r;
    } while (sent < MY_MSG_SIZE);
    if (sent) return sent;
    return r;
}

int my_msg_recv (int sock, void *msg) {
    int r, rcvd = 0;
    do {
        r = recv(sock, (char *)msg + rcvd, MY_MSG_SIZE - rcvd, MSG_WAITALL);
        if (r <= 0) {
            if (r < 0 && errno == EINTR) continue;
            break;
        }
        rcvd += r;
    while (rcvd < MY_MSG_SIZE);
    if (rcvd) return rcvd;
    return r;
}

Notice that the software still has to deal with certain error cases. In the case of EINTR, the I/O operation needs to be retried. For other errors, the delivery or retrieval of data may be incomplete. But generally, for blocking sockets, we expect only one iteration for the do-while loops above.

If your messages are not always the same size, then you need a way to frame the messages. A framed message means you need a way to detect the start of a message, and the end of a message. Perhaps the easiest way to frame a message over a streaming socket is to precede a message with its size. Then the receiver would first read out the size, and then read the rest of the message. You should be able to easily adapt the sample code for my_msg_send and my_msg_recv to do that.

Finally, there is the question of your message itself. If the messages are not always the same size, this likely means there are one or more variable length records within the message. Examples are an array of values, or a string. If both the sender and receiver agree to the order of the records, then it is enough to precede each variable length record with its length. So suppose your message had the following structure:

struct my_data {
    const char *name;
    int address;
    int *types;
    int number_of_types;
};

Then you could represent an instance of struct my_data like this:

NAME_LEN : 4 bytes
NAME : NAME_LEN bytes
ADDRESS : 4 bytes
TYPES_LEN : 4 bytes
TYPES : TYPES_LEN * 4 bytes

NAME_LEN would be obtained from strlen(msg->name), and TYPES_LEN would obtain its value from msg->number_of_types. When you send the message, it would be preceded by the total length of the message above.

We have been using 32 bit quantities to represent the length, which is likely sufficient for your purposes. When transmitting a number over a socket, the sender and receiver has to agree on the byte order of the number. That is, whether the number 1 is represented as 0.0.0.1 or as 1.0.0.0. This can typically be handled using network byte ordering, which uses the former. The socket header files provides the macro htonl which converts a 32 bit value from the host's native byte order to network byte order. This is used when storing a value in, say NAME_LEN. The receiver would use the corresponding macro ntohl to restore the transmitted value back to a representation used by the host. The macros could of course be no-ops if the hosts native byte ordering matches network byte order already. Using these macros is of particular importance when sending and receiving data in a heterogeneous environment, since the sender and receiver may have different host byte orderings.

Upvotes: 0

Karthick S
Karthick S

Reputation: 3304

If your data is related, you can create a struct in a separate header and use it in both the client and server code and send a variable of this struct across. If it is not related, then I am not sure why you would need to send them across as one single message.

Upvotes: 0

slashmais
slashmais

Reputation: 7155

There's no guarantee that you can send/receive all your data in a single read/write operation; too many factors may influence the quality/packet-size/connection-stability/etc.
This question/answer explains it.

Some C-examples here.
A good explanation of socket programming in C.
A quick overview of TCP/IP.

About sending different types of messages:
The data you send is from your server-app is received by your client-app who then can interpret this data any way it likes.

Upvotes: 2

Related Questions