views:

53

answers:

4

First of all, I've never worked with C before (mostly Java which is the reason you'll find me write some naive C code). I am writing a simple command interpreter in C. I have something like this:

//Initialization code

if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
    perror("Select dead");
    exit(EXIT_FAILURE);
}

....
....
//Loop through connections to see who has the data ready
//If the data is ready
if ((nbytes = recv(i, buf, sizeof(buf), 0)) > 0) {
     //Do something with the message in the buffer
}

Now if I'm looking at something like a long paragraph of commands, it is obvious that a 256 byte buffer will not be able to get the entire command. For the time being, I'm using a 2056 byte buffer to get the entire command. But if I want to use the 256 byte buffer, how would I go about doing this? Do I keep track of which client gave me what data and append it to some buffer? I mean, use something like two dimensional arrays and such?

+2  A: 

Yes, the usual approach is to have a buffer of "data I've received but not processed" for each client, large enough to hold the biggest protocol message.

You read into that buffer (always keeping track of how much data is currently in the buffer), and after each read, check to see if you have a complete message (or message(s), since you might get two at once!). If you do, you process the message, remove it from the buffer and shift any remaining data up to the start of the buffer.

Something roughly along the lines of:

for (i = 0; i < nclients; i++)
{
    if (!FD_ISSET(client[i].fd, &read_fds))
        continue;

    nbytes = recv(client[i].fd, client[i].buf + client[i].bytes, sizeof(client[i].buf) - client[i].bytes, 0);

    if (nbytes > 0)
    {
        client[i].bytes += nbytes;

        while (check_for_message(client[i]))
        {
            size_t message_len;

            message_len = process_message(client[i]);
            client[i].bytes -= message_len;
            memmove(client[i].buf, client[i].buf + message_len, client[i].bytes);
        }
    }
    else
        /* Handle client close or error */
}

By the way, you should check for errno == EINTR if select() returns -1, and just loop around again - that's not a fatal error.

caf
+2  A: 

I would keep a structure around for each client. Each structure contains a pointer to a buffer where the command is read in. Maybe you free the buffers when they're not used, or maybe you keep them around. The structure could also contain the client's fd in it as well. Then you just need one array (or list) of clients which you loop over.

The other reason you'd want to do this, besides the fact that 256 bytes might not be enough, is that recv doesn't always fill the buffer. Some of the data might still in transit over the network.

If you keep around buffers for each client, however, you can run into the "slowloris" attack, where a single client keeps sending little bits of data and takes up all your memory.

Dietrich Epp
Great! Never knew about the slowloris attack... Thanks a lot to others as well...
Legend
+1  A: 

It can be a serious pain when you get tons of data like that over a network. There is a constant trade between allocating a huge array or multiple reads with data moves. You should consider getting a ready made linked list of buffers, then traverse the linked list as you read the buffers in each node of the linked list. That way it scales gracefully and you can quickly delete what you've processed. I think that's the best approach and it's also how boost asio implements buffered reads.

Chris H
+1  A: 

If you're dealing with multiple clients a common approach to to fork/exec for each connection. Your server would listen for incoming connections, and when one is made it would fork and and exec a child version of itself that would then handle the "command interpreter" portion of the problem.

This way you're letting the OS manage the client processes--that is, you don't have to have a data structure in your program to manage them. You will still need to clean up child processes in your server as they terminate.

As for managing the buffer...How much data do you expect before you post a response? You may need to be prepared to dynamically adjust the size of your buffer.

Rob Jones
I was actually thinking of checking for a EOF character which my protocol has... So I guess what I would do is keep appending this buffer to the main data line until I actually see this character.. What do you think?
Legend
I would be afraid of not having some kind of guard. Do you trust the sender of the data to behave--to always send well-formed data? What about the network? What about the OS? I wouldn't blindly append data of unknown origin to a buffer without a bit of protection in place. Even if you wrote the client, the data is still of unknown origin, i.e. don't trust yourself.
Rob Jones
I see... Got your logic... Thanks for the pointers...
Legend