Coding Dork
Coding Dork

Reputation: 23

C client server with <socket.h>

Hello and good evening,

I am writing a client & server pair of programs in C. Right now I got both of them running and by providing access to ports on my server through router settings, I can establish local and global connections for a barebones chat back and forth. Here is what both programs do until now.

Client:

Server:

My question:

What do I have to consider for handling and error-handling these individual steps correctly?

What I got until now is code like this:

printf("trying to connect to: IPv4 <%s> port <%s> ...", argv[1], argv[2]);

if(connect(net_socket, (struct sockaddr *)&server_address, sizeof server_address) == -1)
{
    fprintf(stderr, "\ntcp connection failed.\n");
    exit(4);
}

printf(" success.\nconnection to server: IPv4 <%s> port <%s>\n", argv[1], argv[2]);

Code like this is wrapped around all the steps these programs do from socket creation to connect and accept. And they work given the right input.

I want to scale my programs:

etc.

For that I need to know how to do the backbone correct and I want to get the basics straight before I start multithreading, forking, selecting or building whatnot around the core.

I don't want to overscale this post, hence I will only post my code if it is requested at all. Thank you very much for reading and replying in advance.

Upvotes: 2

Views: 463

Answers (1)

Dellowar
Dellowar

Reputation: 3352

First off, it's always good practice to wrap all error/loging functions so they can be modified for later scaling. I do this so, later I can add file/line to my errors.

void perr(char *message)
{
    fprintf(stderr, message);
}

On to your question. It's a very practice-rich answer. (Better asked on this board. But I'll help you anyways.

You're right to have the backbone as solid as possible before expanding. The best practice for a well made application is to have it very 'flat' in scope. (not to many if's in if's). Doing this will also provide you with a better understanding of the flow of code and more atomic error reporting:

main()
{
    if(!(soc = createsocket(...)) return 1;
    if(!bindsocket(soc))
    {
         deletesocket(soc);
         return 1;
    }

    // Main server loop
    while(req = accept_request(soc))
    {
        proc_request(req);
    }
    plog("No longer accepting request");
    return 0;
}

Notice how I have no error reporting in the driver function (main in this example). This is because all error reporting is handled INSIDE of the called functions (createsocket(), bindsocket(), ect).

The driver function is only concerned if the called functions succeeded or not. The details of each failure are best described in the function who's arguments lead to the failure.

Here are my tips:

  • Avoid exit(), your code needs to be ready to use return all the way up to main() if something goes wrong. There are execptions (threads and child procs, but you get the idea)
  • Good programs are good at failing
  • For a server/client relationship, the server is typically single threaded and only loops through all client requests (computers are faster than you think, unless you're expecting around 5k requests).
  • State Machines are extremely useful in your situation. Think of the states your program is going to be in between start and stop, lay out what methods need to be in each state and have an overall plan what happens with errors in each state. (ie, opened, establishing, running, stopping)

Upvotes: 1

Related Questions