Solved

Simple Server Code for C/C++ Need to be Clarified ASAP .... Linux Socket Programming

Posted on 2008-10-29
12
1,463 Views
Last Modified: 2013-11-13
I attached the code below. Just look at the code between asterisks.
This is my first experience in Client-Server code. Please answer my
questions as i got no tutor who can guide me when i need help.

Note: i will be searching on what i have asked here, but it would
be nice to get help from experts on the specific questions i have
which will help me understand the concepts better.

Here is the code without my comments:
http://beej.us/guide/bgnet/examples/server.c

Try to answer what you can. I hope to hear from you!

Q1: what is the function for? what is WNOHANG? why do we need it?

Q2: what is this for? why do we need it?

Q3: the comment says loop through all the results & bind to the first we can!
 We only have one port "3490"  and one local machine, so we have one result
& we will bind to 127.0.1.1 on 3490!  How come the comment says " bind to the
first we can", when is it not going to bind to the first time? why?

Q4: Is this optional to be in my code? Isn't socket() already specfied the family
, socktype & protocol, so why do we use setsockopt?

Q5: we only listen once, so the we will only accept() one connection.
I think if we want to listen to all connections, we should go through a
loop. Can you explain to me please?

Q6: please explain to me what does this block of code do on each line? why
do we need it?

Q7: when will while(1) turn to false? we are not comparing it to anything!

Q8: when declared their_addr at beginning of the code, but we never touched
during coding. How come is it passed as a parameter in accept()?

Q9: when accept() fails, new_fd holds -1 for error.
We should "close(new_fd)" before doing "continue;"
because as far as i know every connection or acception
should have a new file descriptor. Why we did not put
close(new_fd)?

Q10: what this step for? ntop() for making the address
readable to screen. We do not print any ip's in this code!

Q11: i have not dealt with forks() before. Why do we close
sockdf if fork() is false? Aren't we suppose to go to the next
loop "continue;" instead exit() to receive accept() the next
connection?  What if fork() is true, how can we the message
"hello world"?

Q12: what does "parent" in the comment refer to?


Thanks!
/*

** server.c -- a stream socket server demo

*/
 

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#include <arpa/inet.h>

#include <sys/wait.h>

#include <signal.h>
 

#define PORT "3490"  // the port users will be connecting to

#define BACKLOG 10   // how many pending connections queue will hold
 
 

//**********************Q1**********************//

void sigchld_handler(int s)

{

    while(waitpid(-1, NULL, WNOHANG) > 0);

}

//**********************************************//
 
 

//get sockaddr, IPv4 or IPv6:

void *get_in_addr(struct sockaddr *sa)

{

     if (sa->sa_family == AF_INET) {

         return &(((struct sockaddr_in*)sa)->sin_addr);

     }

     return &(((struct sockaddr_in6*)sa)->sin6_addr);

}
 
 

int main(void)

{

     int sockfd, new_fd; // listen on sock_fd, new connection on new_fd

     struct addrinfo hints, *servinfo, *p;

     struct sockaddr_storage their_addr; // connector's address information

     socklen_t sin_size;
 

     //**********************Q2**********************//

     struct sigaction sa;

     //**********************************************//
 

     int yes=1;

     char s[INET6_ADDRSTRLEN];

     int rv;
 

     memset(&hints, 0, sizeof hints);

     hints.ai_family = AF_UNSPEC;

     hints.ai_socktype = SOCK_STREAM;

     hints.ai_flags = AI_PASSIVE; // use my IP
 

     if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) == -1) {

         fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));

         return 1;

     }
 

     //*************Q3*********//

     // loop through all the results and bind to the first we can

     for(p = servinfo; p != NULL; p = p->ai_next) {
 

         if ((sockfd = socket(p->ai_family, p->ai_socktype,

                 p->ai_protocol)) == -1) {

             perror("server: socket");

             continue;

         }

     //************END***********//
 

         //*************Q4*********//

         if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,

                 sizeof(int)) == -1) {

             perror("setsockopt");

             exit(1);

         }

         //************END***********//
 

         if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {

             close(sockfd);

             perror("server: bind");

             continue;

         }
 

      break;

  }
 

  if (p == NULL) {

      fprintf(stderr, "server: failed to bind\n");

      return 2;

  }
 

  freeaddrinfo(servinfo); // all done with this structure
 

  //*********************Q5************************//

  if (listen(sockfd, BACKLOG) == -1) {

      perror("listen");

      exit(1);

  }

  //**********************END**********************//
 
 

  //**********************Q6**********************//

  sa.sa_handler = sigchld_handler; // reap all dead processes

  sigemptyset(&sa.sa_mask);

  sa.sa_flags = SA_RESTART;

  if (sigaction(SIGCHLD, &sa, NULL) == -1) {

      perror("sigaction");

      exit(1);

  }

  //**********************END**********************//
 

  printf("server: waiting for connections...\n");
 

  //**********Q7***********//

  while(1) { // main accept() loop

  //**********END**********//
 

      //*************Q8***********//

      sin_size = sizeof their_addr;

      new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);

      //*************END**********//
 

      //*************Q9***********//

      if (new_fd == -1) {

          perror("accept");

          continue;

      }

      //*************END**********// 

 

      //***********Q10**********//

      inet_ntop(their_addr.ss_family,

          get_in_addr((struct sockaddr *)&their_addr),

          s, sizeof s);

      //***********END***********//
 

      printf("server: got connection from %s\n", s);
 

      //***********Q11**********//

      if (!fork()) { // this is the child process

          close(sockfd); // child doesn't need the listener

          if (send(new_fd, "Hello, world!", 13, 0) == -1)

              perror("send");

          close(new_fd);

          exit(0);

      }

      //***********END***********//
 

      //***********Q12***********//

      close(new_fd); // parent doesn't need this

      //***********END***********//
 

  }

  //**********************END**********************//
 

  return 0;

}

Open in new window

0
Comment
Question by:F-J-K
  • 7
  • 5
12 Comments
 
LVL 53

Accepted Solution

by:
Infinity08 earned 500 total points
ID: 22831952
>> Q1: what is the function for? what is WNOHANG? why do we need it?

>> Q2: what is this for? why do we need it?

>> Q6: please explain to me what does this block of code do on each line? why
>> do we need it?

These three questions can be answered together.

The sigaction function sets a signal handler (defined in the sigaction struct). In this case, whenever the SIGCHLD signal occurs (meaning that a child process terminated), the function sigchld_handler is called.

sigchld_handler will get the status information for the terminated child processes (WNOHANG means that the calling thread is not suspended if that status information is not immediately available - remember that the SIGCHLD signal indicates that such information is available)



>> Q3: the comment says loop through all the results & bind to the first we can!
>>  We only have one port "3490"  and one local machine, so we have one result
>> & we will bind to 127.0.1.1 on 3490!  How come the comment says " bind to the
>> first we can", when is it not going to bind to the first time? why?

The host might have multiple IP addresses. Yours apparently has only one (127.0.1.1, which btw is a weird IP address - are you sure your network is correctly configured ?), but others might have more than one (one multiple network interfaces or even on the same network interface).
The bind request might fail, for example, because the port on that specific IP address is already bound.



>> Q4: Is this optional to be in my code? Isn't socket() already specfied the family
>> , socktype & protocol, so why do we use setsockopt?

The SO_REUSEADDR option is set to tell that even if the port is in TIME_WAIT, it should still be re-used for the current socket anyway.
This is done for the case where the server was shut down, and restarted again, and the socket on the port is still active.
This is necessary in order to be able to restart the server "instantaneously".



>> Q5: we only listen once, so the we will only accept() one connection.
>> I think if we want to listen to all connections, we should go through a
>> loop. Can you explain to me please?

The listen call simply starts listening on the specified socket for any connections that come in. After the listen call, such incoming connections can be accept'ed. Note that you don't have to listen again (it is still listening after an accept), and can simply accept the next connection after handling the first, etc.

The accept itself however has to be in a loop, so that multiple connections can be accepted.



>> Q7: when will while(1) turn to false? we are not comparing it to anything!

It won't. This is an infinite loop. We want to listen forever, and accept all connections that come in. This is a server after all, and it should not stop running.

The only way we get out of the loop is in case of a crash or a user-initiated shutdown of the server.

Note that you can get out of an infinite loop using 'break', 'return' or 'goto' statements.



>> Q8: when declared their_addr at beginning of the code, but we never touched
>> during coding. How come is it passed as a parameter in accept()?

accept will store the address information of the client in that struct. So, if you want to know the IP address of the client that connected to your server, you can find it there after the accept call.



>> Q9: when accept() fails, new_fd holds -1 for error.
>> We should "close(new_fd)" before doing "continue;"
>> because as far as i know every connection or acception
>> should have a new file descriptor. Why we did not put
>> close(new_fd)?

Because when accept fails, it means it did not create a valid socket (indicated by the -1). So, you don't have to close the socket either, since it was never opened. The connection was not successfully accepted.



>> Q10: what this step for? ntop() for making the address
>> readable to screen. We do not print any ip's in this code!

The code does print the IP address of the client, here :

>>       printf("server: got connection from %s\n", s);

s is filled in by the call to inet_ntop.



>> Q11: i have not dealt with forks() before. Why do we close
>> sockdf if fork() is false? Aren't we suppose to go to the next
>> loop "continue;" instead exit() to receive accept() the next
>> connection?  What if fork() is true, how can we the message
>> "hello world"?

fork creates a new child process, that starts executing at the exact same point. fork returns the child process id to the parent, and 0 to the child. That allows you to distinguish between the parent and the child.
In this case, the parent will skip the if block (since fork returns a non-zero value), and continue with the loop.
The child will run the code in the if block, and then halt itself through the exit(0) call at the end of the if block.

The sockfd socket is closed, since the child doesn't need access to the listen socket. It only needs the client connection socket (new_fd).



>> Q12: what does "parent" in the comment refer to?

It refers to the parent process - ie. the main process that listens for incoming connection requests, accepts them, and forks off child processes to handle the accepted connections.
0
 
LVL 1

Author Comment

by:F-J-K
ID: 22839733
GREAT! I'll read it later on & see if i got any questions! Appreciated Sir.
0
 
LVL 1

Author Comment

by:F-J-K
ID: 22842387
>>The SO_REUSEADDR option is set to tell that even if the port is in TIME_WAIT, it should still be re-used for the current socket anyway.

TIME_WAIT means both sides have agreed to close and TCP
must now wait a prescribed time before taking the connection
down, so SO_REUSEADDR just stops the time wait & re-use the port number. right?

------------------------------------------------------------------------------------
I have read this somwhere:

" This socket option tells the kernel that even if this port is busy (in
  the TIME_WAIT state), go ahead and reuse it anyway.  If it is busy,
  but with another state, you will still get an address already in use
  error."

 This socket option tells the kernel that even if this port is busy (in
  the TIME_WAIT state), go ahead and reuse it anyway.>> when port is busy, it means its dealing with another state. There shouldn't be any TIME_WAIT until  both sides have agreed to close.

------------------------------------------------------------------------------------
>>If it is busy, but with another state, you will still get an address already in use
  error.

Of course, if its busy, then its dealing with another state. Otherwise, it means both sides have agreed to close, so TIME_WAIT appears.

------------------------------------------------------------------------------------
>>you will still get an address already in use error.
Didn't we use SO_REUSEADDR option to enable having two sockets with the same Internet port number?!

------------------------------------------------------------------------------------
In msdn documentation describes SO_REUSEADDR as follows:
allows the socket to be bound to an address that is already in use.

We are always be bound to our local address. I kind do not get it now, i think it should be
allows the socket to be bound to "a port" that is already in use.

Please clarify if you don't mind...

------------------------------------------------------------------------------------

I'm very new to these topics. You reply is awesome, it guided me to learn things i was not aware about it. I did not have a clue about parent & child processes, your answer gave me lots of hints on child & parent processes.
0
 
LVL 53

Expert Comment

by:Infinity08
ID: 22842980
>> so SO_REUSEADDR just stops the time wait & re-use the port number. right?

Correct. Since we know that the server that was previously listening on that port is no longer listening (because it has crashed), this is ok.


>> when port is busy, it means its dealing with another state.

"busy" in this context means that the port is currently "assigned to a task". TIME_WAIT is such a "task". So, if a port is in TIME_WAIT, it is still considered busy. Only when it leaves the TIME_WAIT state (usually after one or more minutes) will the port become available (ie. not busy).


>> Didn't we use SO_REUSEADDR option to enable having two sockets with the same Internet port number?!

No, that's not what SO_REUSEADDR is used for. It's used to recycle a port in TIME_WAIT state, and create a new socket for it, even though that port is still busy (in TIME_WAIT).


>> I kind do not get it now

Remember that a socket is a combination of 4 values : IP address and port of both ends. It is not just a port.



>> I did not have a clue about parent & child processes, your answer gave me lots of hints on child & parent processes.

forking is quite common. It's not an easy subject, but it is an important thing to know and master, especially for server programming.
0
 
LVL 1

Author Comment

by:F-J-K
ID: 22842989
>>WNOHANG means that the calling thread is not suspended if that status information is not immediately available - remember that the SIGCHLD signal indicates that such information is available

The parent proccess will be suspended by default until child process terminates. WNOHANG flag to indicates that the parent process shouldn't wait & it should keep processing in producing more children. right?
0
 
LVL 1

Author Comment

by:F-J-K
ID: 22843035
>>whenever the SIGCHLD signal occurs (meaning that a child process terminated), the function sigchld_handler is called.

sigaction has &sa in the middle, it points to the whole sigaction structure. We should pass sa.sa_handler instead of &sa, because when SIGCHLD occurs we have to call sa.sa_handler. sa.sa_handler carries sigchld_handler function, because sigchld_handler is called from the second parameter. Please clarify why we did not pass a pointer to sa.sa_handler specifically.


>>The sigaction function sets a signal handler (defined in the sigaction struct).
we should define any sigchld_handler() in our .cpp because as you mentioned its already defined in sigaction struct. Any idea?
0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 
LVL 53

Expert Comment

by:Infinity08
ID: 22843113
>> WNOHANG flag to indicates that the parent process shouldn't wait & it should keep processing in producing more children. right?

No.
The signal handler is only called when a child terminates. The parent (in the signal handler) then simply get the status information of that terminated child, and continues processing.
The WNOHANG is there to make sure that the server is not blocked during all this. ie. if no more children have terminated, then we don't wait for one to terminate. The next child that terminates will generate another signal, and at that time the signal handler will get its status information.


>> We should pass sa.sa_handler instead of &sa,

No, the second parameter of the sigaction function is a pointer to a sigaction struct :

        http://linux.die.net/man/2/sigaction

So, that's what you need to pass it.



>> we should define any sigchld_handler() in our .cpp because as you mentioned its already defined in sigaction struct. Any idea?

sigchld_handler is the actual signal handler. It's what we want to happen when a SIGCHLD signal occurs.
The sigaction struct simply groups some information (a pointer to the signal handler and a few more options) in order to pass it to the sigaction function.
0
 
LVL 1

Author Comment

by:F-J-K
ID: 22844354
Ok good, i appreciate your help, please bear with me.

>> if no more children have terminated, then we don't wait for one to terminate. The next child that terminates will generate another signal, and at that time the signal handler will get its status information.

1. so you want to tell me that a parent process only works when its children process still processing. If all children processes have terminated. parent process will hang & wait for nothing. WNOHANG duty is to make the parent process always working regardless whether all children have terminated or not. If this is right, what kind of thing our parent process does when there is no more children to be terminated? The main parent process duty is produce children processes who do the work, if no more child process, parent process got nothing to do. Can you comment on what i have understood.

2. The code example i gave, is it a single threaded or multi-threaded?

3. The example code, only produce one child process, because it only sends back to the client a  message "Hello World", then close the program. Am i right? I have not tested the code yet, i do not see a point of testing a code if i do not know whats going on inside this code.

>>No, that's not what SO_REUSEADDR is used for. It's used to recycle a port in TIME_WAIT state, and create a new socket for it, even though that port is still busy (in TIME_WAIT).

4. setsockopt() does not create a new socket. socket() is the one which creates a new socket & then be passed into setsockopt(). SO_REUSEADDR recycle a port in TIME_WAIT state which the port is still busy, we are kind of inturrupting the port of doing its job completely, because i think TIME_WAIT state is very important as it ensures all data stream reach with no corruption?! Any idea. Please help

0
 
LVL 53

Expert Comment

by:Infinity08
ID: 22844525
>> 1. so you want to tell me that a parent process only works when its children process still processing.

No, a parent process works, unless it's blocked. Calling waitpid without WNOHANG would block the parent until a child terminates. To avoid the parent blocking on this, WNOHANG is used.
We don't want the parent to block, since that would mean it can't accept any new connections, and that's not generally how we want a server to behave :)

In this server code, the parent's job is to accept incoming connections. For each of these connections it forks off a child process (this child process will handle that specific connection). When there are no incoming connections, the server just waits until there is an incoming connection.

Now, the SIGCHLD signal occurs in the parent whenever a child terminates. This way the parent knows that the child has terminated, and it can get its status. That is done by the signal handler (which will be run whenever the SIGCHLD signal occurs).


>> 2. The code example i gave, is it a single threaded or multi-threaded?

It is a multi-process server. One process (the parent) accepts incoming connections. The other processes are the workers (the children), and they process one connection each.


>> The example code, only produce one child process

It forks off one child process for each incoming connection. If there are 10 incoming connections, it will fork off 10 child processes.


>> setsockopt() does not create a new socket.

No, but setsockopt sets an option for the socket (set sock(et) opt(ion)).


>> because i think TIME_WAIT state is very important as it ensures all data stream reach with no corruption?!

Exactly. TIME_WAIT IS very important. But in this specific case, the server has crashed (or otherwise ended), so even if there is more data to be received, the server is no longer there to receive it. So, it is safe to re-use the socket for the newly started server.
0
 
LVL 1

Author Comment

by:F-J-K
ID: 22845642
Wonderful! Even though i got some more questions, i will just stop by here. I think i'm trying to take it too much at once. I will go ahead and get practice with a single threaded an echo server "Hello World" & try to get used to some simple codings in client-server single thread. After that, it will be easier for me to learn forks() and multi-threaded. All your answers helped me in understanding forks() & lots of things i did not know. I saved your answers into a file, so i will have a second look at it when i start learning about forks() officially later on. Thanks Infinity08! :-)
0
 
LVL 1

Author Closing Comment

by:F-J-K
ID: 31511144
You deserve over 500 points. No wonder you have been specified as a Genius!
0
 
LVL 53

Expert Comment

by:Infinity08
ID: 22845675
No problem. If you want to read up more on forking, check this out :

        http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
java ^ examples 8 57
wordsFront challenge 8 66
word0 challenge 4 52
How to print into std::string like sprintf using C++11? 12 37
Summary: This tutorial covers some basics of pointer, pointer arithmetic and function pointer. What is a pointer: A pointer is a variable which holds an address. This address might be address of another variable/address of devices/address of fu…
If you haven’t already, I encourage you to read the first article (http://www.experts-exchange.com/articles/18680/An-Introduction-to-R-Programming-and-R-Studio.html) in my series to gain a basic foundation of R and R Studio.  You will also find the …
The viewer will learn how to implement Singleton Design Pattern in Java.
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now