Fabio Carello
Fabio Carello

Reputation: 1102

Simple TCP-Server

This is a basic TCP-Server implementation for teaching purposes. Are there any error or improvements to do. Any suggest is welcome!

I only have a doubt:

signal(SIGCHLD, SIG_IGN);

Is that call used to prevent zoombie-child processes?

 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <netdb.h>

 #include <sys/signal.h>

 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>

 #define BACKLOG 5
 #define MAXSIZE 1024 //max-bytes for read-buffer

void main(){

int sock_ds, ret, length;
int acc_ds; //Accept socket descriptor

struct sockaddr_in addr; //this addres
struct sockaddr rem_addr; //remote address (generic)

char buff[MAXSIZE];

sock_ds = socket(AF_INET, SOCK_STREAM, 0); // => TCP

bzero((char *)&addr, sizeof(addr)); //reset struct
addr.sin_family = AF_INET;
addr.sin_port = htons(25000);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(sock_ds, &addr, sizeof(addr));
if(ret == -1){
    perror("Binding error");
    exit(1);
}

ret = listen(sock_ds, BACKLOG); // backlog queue
    if(ret == -1){
    perror("Listen error");
    exit(1);
}

length = sizeof(rem_addr);
signal(SIGCHLD, SIG_IGN); //zombie children management

/*Busy-waiting (server) and concurrency */
while(1){

    /*Repeat until success*/
    while(acc_ds = accept(sock_ds, &rem_addr, &length) == -1){

        if(fork() == 0){ //child-process

            close(sock_ds); //unused from child
            do{
                read(acc_ds, buff, MAXSIZE);
                printf("Message from remote host:&s\n", buff);

            }while(strcmp(buff, "quit") == 0);
            /*Transimission completed: server response  */
            write(acc_ds, "Reading Done", 10);
            close(acc_ds); //socket closed
            exit(0); //exiting from child
        }
        else{
            close(acc_ds); //unused from parent
        }
    }
}

}

Upvotes: 2

Views: 1515

Answers (3)

ivo Welch
ivo Welch

Reputation: 2866

I need to learn how this works, too. So, I googled "simple tcp-server", found this little program, and fixed up the code to be happier with gcc -Wall and the comments. here is what I put together:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <signal.h>

#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>

#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ptrace.h>


#define BACKLOG 5
#define MAXSIZE 1024 //max-bytes for read-buffer

#define PORT 25000

/****************************************************************/
int main() {

  int sock_ds, ret; unsigned int length;
  int acc_ds; //Accept socket descriptor

  struct sockaddr_in addr; //this address
  struct sockaddr rem_addr; //remote address (generic)

  char buff[MAXSIZE+1];

  if (!(sock_ds = socket(AF_INET, SOCK_STREAM, 0))) perror("socket call failed"); // => TCP

  bzero( (char *)&addr, sizeof(addr)); //reset struct
  addr.sin_family = AF_INET;
  addr.sin_port = htons(PORT);
  addr.sin_addr.s_addr = INADDR_ANY;
  ret = bind(sock_ds, (struct sockaddr *)&addr, sizeof(addr));
  if (ret == -1) {
    perror("Binding error");
    exit(EXIT_FAILURE);
  }

  ret = listen(sock_ds, BACKLOG); // backlog queue
  if (ret == (-1)) {
    perror("Listen error");
    exit(EXIT_FAILURE);
  }

  length = sizeof (rem_addr);
  //  sigaction(SIGCHLD, SA_NOCLDWAIT); //zombie children management

  /*Busy-waiting  (server) and concurrency */
  while (1) {
    fprintf(stderr, "[Waiting for client %d]\n", getpid());

    /*Repeat until success*/
    while ((acc_ds = accept(sock_ds, &rem_addr, &length)) == -1) {
      fprintf(stderr, "[Accepted from client %d]\n", getpid());

      if (fork() == 0) { //child-process
        close(sock_ds); //unused from child
        fprintf(stderr, "[Reading from client %d]\n", getpid());
        while (read(acc_ds, buff, MAXSIZE)) {
          buff[MAXSIZE]='\0';
          printf("Message from remote host:%s\n", buff);
          fflush(stdout);
          if (strncmp (buff, "quit", 5) == 0) break;
        }

        /*Transmission completed: server response  */

        if (write(acc_ds, "Reading Done", 10)) fprintf(stderr, "failed write\n");
        close(acc_ds); //socket closed
        exit(EXIT_SUCCESS); //exiting from child
      }
      else{
        close(acc_ds); //unused from parent
      }
    }
  }
  return EXIT_SUCCESS;
}

two problems. the first is that gnu linux gcc refuses to accept

sigaction(SIGCHLD, SA_NOCLDWAIT); //zombie children management

tcp-server2.c: In function ‘main’: tcp-server2.c:53:3: warning: passing argument 2 of ‘sigaction’ makes pointer from integer without a cast [enabled by default] In file included from tcp-server2.c:7:0: /usr/include/signal.h:267:12: note: expected ‘const struct sigaction * restrict’ but argument is of type ‘int’ tcp-server2.c:53:3: error: too few arguments to function ‘sigaction’ In file included from tcp-server2.c:7:0: /usr/include/signal.h:267:12: note: declared here

the second is that when I telnet to port 25000 and type a few things, nothing ever is echoed as received. so the server does not seem to work. it never gets to accepted from client.

now, I could pick up a programming example from somewhere else, but I thought I should just report this here, so we get a simplest tcp server posted correct here.

Upvotes: 0

user405725
user405725

Reputation:

  1. Return type of main is not int. It should be. Either return EXIT_SUCCESS or EXIT_FAILURE.
  2. Result of socket() call is not checked. It should be, or bind will fail but perror() will tell "Invalid argument" instead of the actual error.
  3. A return value of read() is not checked possibly triggering undefined behavior when printing.
  4. There is no &s format specified, it should be %s.
  5. %s expects a null-terminated string. This is not guaranteed by the code (see point #3). strcmp() may crap out as well.

As for the SIGCHLD, @cnicutar has kindly answered that already, nothing to add there.

Hope it helps. Good Luck!

Upvotes: 3

cnicutar
cnicutar

Reputation: 182674

Yes, that's exactly what ignoring SIGCHLD is for. From TLPI:

There is a further possibility for dealing with dead child processes. Explicitly setting the disposition of SIGCHLD to SIG_IGN causes any child process that subsequently terminates to be immediately removed from the system instead of being converted into a zombie.

It is standard across Unix implementations.

Upvotes: 2

Related Questions