Few rules when dealing with sockets:
1. Low-Level Message Field Definition
Always remember that you are reading and writing to and from stream of bytes. Another rule you have to remember that TCP/IP does not guarantee that one write equals one read. But, what these rules mean to you?
The combination of the above rules means that you have to know exactly how many bytes you need to read before you read from the socket. So:
- (Case 1): if you are reading a fixed-length field, then read as many bytes from the stream as in that field.
- (Case 2): And in the case if you want to read a variable-length field, then pre-pend that field with its byte count before sending.
(Case 1): For example, if you are sending/receiving a four-byte integer, then he code should look like:
int myInt = 12345;
send(sockfd, &myInt, sizeof(myInt), my_flags);
and on the read side:
int myReceivedInt = 0;
int received_count = recv(client_sock, &myReceivedInt, sizeof(myReceivedInt), 0);
Note: Different platforms define different sizes for data types.
(Case 2):
And if you are sending a variable-length field, such as a string, the code should look like:
// get string length
int str_size = strlen(my_str);
// send fixed-length data to pre-pend variable-length field with the latter's size
send(sockfd, &str_size, sizeof(str_size), my_flags);
// send the variable-length field.
send(sockfd, my_str, str_size, my_flags);
and on the receive side:
int string_size = 0;
char *received_string = NULL;
int received_count = recv(client_sock, &string_size, sizeof(string_size), 0);
/*
now we know how big the string is, so we allocate
memory for it or use a previously allocated buffer. This is omitted...
*/
int received_count = recv(client_sock, &received_string, string_size, 0);
received_string[string_size] = '\0';
Another Consequence of the above two rules, is that you may need to read more than once from a socket to receive one full message. This is especially true for larger messages (e.g. file transfer). So, when receiving large messages stick the recv()
in a loop and keep reading until you have read the whole message.
For example if you are reading a file:
int file_size = 0, read_so_far = 0, ret = 0;
recv(sockfd, &file_size , sizeof(file_size), 0);
// now we know how big is the file...allocate buffer (file_content) and read file fully
while(read_so_far != file_size)
{
ret = recv(sockfd, file_content + read_so_far, file_size - read_so_far, 0);
if(ret < 0)
{
// handle error case, socket reset maybe?
perror("Something bad happened with the socket");
break;
}
read_so_far += ret;
}
2. Defining a Protocol
Now that you know the low-level stuff, you can define the protocol. The protocol is a description of the format of messages to be sent a received over a connection. And each message is composed of one ore more fields like the ones above.
3. Defining a Message
Bear in mind that each message should define a command and arguments for the command. For example, a message to a delete a file should include the command "delete" and "file name". So, a delete file message may be defined as:
- 1 byte for command + 4 bytes for name_size + variable-length file_name.
Do the same for other messages and you will end up with the protocol. Code may look like:
char command = 0;
s = recv(client_sock, &command, 1,0);
if(command == LIST_NODES_COMMAND)
{
// read arguments for command and process it.
} else if(command == CREATE_FOLDER_COMMAND)
{
// read arguments for command and process it.
}
4. Message Handling Loop
The final step in the server is stick the message handling in a loop:
while(1)
{
char command = 0;
s = recv(client_sock, &command, 1,0);
if(command == LIST_NODES_COMMAND)
{
// handle list command here.
} else if(command == CREATE_FOLDER_COMMAND)
{
// read params like above and handle command.
} else if (command == COMMAND_QUIT)
{
// do the stuff necessary before the client disconnects.
}
}