Reputation: 23
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:
create a socket
create an sockaddr_in (port and IP address)
connect the socket with the sockaddr_in to the server
go into a loop for read / write to the server, one at a time
Server:
create a socket
create a sockaddr_in (port and IP address)
bind the socket to sockaddr_in
listen to request on the socket
accept a request
go into a loop for read / write to the server, one at a time
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:
simultaneous read and write for both sides
multiple clients to server instead of client and server
correct data encapsulation
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
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:
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)opened
, establishing
, running
, stopping
)Upvotes: 1