/* thread_echoserver.c - Simple TCP server that accepts connections
 *            and echoes back everything it receives.
 *            It listens on port PROTOPORT set to 5194
 *            or to the port you specify on command line.
 *            Compile as
 *              % gcc -o thread_echoserver thread_echoserver.c -L/usr/shlib -lpthread
 *            Run as
 *              % thread_echoserver [port] &
 *            It can handle two concurrent connections with two 
 *            worker threads. The worker threads compete for
 *            connections using a mutex. [A better, but more complex,
 *            solution would use the select function.]
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>

// If we are waiting reading from a pipe and
// the interlocutor dies abruptly (say because
// of ^C or kill -9), then we receive a SIGPIPE
// signal. Here we handle that.
void sig_pipe(int n) 
{
   fprintf(stderr, "Broken pipe signal\n");
}

extern int errno;

/*In an infinite loop, it reads characters from sockfd and writes them
  to the same socket.
*/
void
chr_echo(int sockfd)
{
  ssize_t n;
  char c;
  
  for ( ; ; ) {
    n = read(sockfd, &c, 1);
    if (n > 0) {
       write(sockfd, &c, 1);
    } else if ( n < 0) { 
       fprintf(stderr, "Negative return from Read\n");
       if (errno == EINTR) /* IO was interrupted by a signal */
           continue;
       return;
    } else  /* connection closed by other end */
      return;
  }
}

pthread_mutex_t mutex; /* used to protect access to the listening socket

/* In a loop it obtains a connection from the listening socket *sd 
   and responds to requests from the connected socket. When the client closes the
   connection the loop is iterated.
 */
void worker(void * sd)
{
  struct sockaddr_in cad;    /* structure to hold client's address*/
  int    alen = sizeof(cad); /* length of address                 */
  int    sd2;                /* connected socket descriptor */

  /* Main server loop - accept and handle requests */
  for ( ; ; ) {
    /* Obtain a connection sd2 from listening socket *sd */
    if (pthread_mutex_lock(&mutex)) {
	perror("pthread_mutex_lock");
	exit(1);
    }
    if ((sd2 = accept(*((int *)sd), (struct sockaddr *)&cad, &alen)) < 0){
      if (errno == EINTR) continue;
      perror("accept failed\n");
      exit(1);
    }
    if (pthread_mutex_unlock(&mutex)) {
	perror("pthread_mutex_unlock");
	exit(1);
    }  

    chr_echo(sd2);
    close(sd2);
  }
}


#define PROTOPORT 5194 /* default protocol port number*/
#define QLEN 6

int
main(int argc, char *argv[])
{
  struct sockaddr_in sad;/* structure to hold server's address*/
  int    sd;             /* listening socket descriptor       */
  int    port;           /* protocol port number              */
  int    option_value;   /* needed for setsockopt             */
  pthread_t tid1, tid2;  /* two thread ids                    */
 
  /* Check command-line argument for protocol port and extract
   * port number if one is specified. Otherwise, use the default
   * port value given by constant PROTOPORT
   */
  port = (argc > 1)?atoi(argv[1]):PROTOPORT;
    
  /* clear and initialize server's sockaddr structure */
  memset((char *)&sad, 0, sizeof(sad)); 
  sad.sin_family = AF_INET;               /* set family to Internet */
  sad.sin_addr.s_addr = INADDR_ANY;       /* set the local IP address */
  sad.sin_port = htons((u_short)port);    /* Set port */
  
  /* Create socket */
  if ( (sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    fprintf(stderr, "socket creation failed\n");
    exit(1);
  }

  /* Make listening socket's port reusable - must appear before bind */
  if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&option_value, 
	sizeof(option_value)) < 0) {
	fprintf(stderr, "setsockopt failure\n");
	exit(1);
  }
  
  /* Bind a local address to the socket */
  if (bind(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
    fprintf(stderr, "bind failed\n");
    exit(1);
  }
  
  /* Specify size of request queue */
  if (listen(sd, QLEN) < 0) {
    fprintf(stderr, "listen failed\n");
    exit(1);
  }
 
  /* Establish handling of the SIGPIPE signal */
  if (signal(SIGPIPE, sig_pipe) == SIG_ERR) {
     fprintf(stderr, "Unable to set up signal handler\n");
  }

  /* initialize mutex */
  if (pthread_mutex_init(&mutex, NULL)) {
      perror("pthread_mutex_init");
      exit(1);
  }

  /* create worker threads. Each worker thread will compete on mutex to accept a connection.
     Once a worker thread has a connection, it releases the mutex so that another thread can
     get the connection. */
  if (pthread_create(&tid1, NULL, (void *)worker, &sd) != 0){
       perror("pthread_create");
            exit(1);
  }
  if (pthread_create(&tid2, NULL, (void *)worker, &sd) != 0){
       perror("pthread_create");
            exit(1);
  }

  /* wait for threads to terminate - they shouldn't */
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);  
}











