/***********************************************************
 * multiplex echo server                                   *
 * demonstration for a multiplex server                    *
 * Verteilte Systeme II Kapitel 6                          *
 ***********************************************************
 * 1998 by Frank Kargl (frank.kargl@rz.uni-ulm.de)         *
 ***********************************************************
 * Usage: echos <port> (default port = 1099)               *
 ***********************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* default port to use */
#define DEFPORT 1099
/* max line length */
#define MAXLINE 1024
/* max num of connections */
#define MAXCON FD_SETSIZE

/* job control block */
struct job_entry_t {
    int socket;				/* socket assoc. with this job */
    int linec;				/* line counter */
    FILE *fpin, *fpout;			/* corresponding FILE ptr */
};
	
/***********************************************************
 *                                                         *
 * Function:                                               *
 * void usage(char* name)                                  *
 * print usage message                                     *
 * Parameters:                                             * 
 *  name - name of executable                              *
 * Return:                                                 *
 *  -                                                      *
 *                                                         *
 ***********************************************************/

void usage(char* name) {
    printf("%s - a simple echo server\n", name);
    printf("Usage: %s <port> (default port = %d)\n", name, DEFPORT);
    exit(1);
}

int main(int argc, char** argv) {
    
    int lsocket;			/* listen socket */
    int asocket;			/* accept socket */
    int port = DEFPORT;			/* port to use */
    struct sockaddr_in servaddr;	/* server address */
    struct sockaddr_in cliaddr;		/* client address */
    int cliaddr_len;			/* length of client address */
    int ret;				/* generic return value */
    struct job_entry_t job_entry[MAXCON]; /* array for job control block */
    fd_set rset;			/* fd set for select */
    int maxfd = -1;			/* maxfd used */
    int i;
    char line[MAXLINE];			/* line buffer */

    /* init job entry and fd_set */
    for(i=0; i<MAXCON; i++) {
	job_entry[i].socket = -1;
	job_entry[i].linec = 1;
    }

    /* check for port */
    if (argc == 2) {
	port = atoi(argv[1]);
    } else if (argc != 1) {
	usage(argv[0]);
    }
    
    /* open listen socket */
    lsocket = socket(AF_INET, SOCK_STREAM, 0);
    if (lsocket == -1) {
	perror("Can't open lsocket");
	exit(1);
    }
    
    /* bind socket to port */
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
    ret = bind(lsocket, (struct sockaddr*) &servaddr, sizeof(servaddr));
    if (ret == -1) {
	perror("Can't bind lsocket");
	exit(1);
    }
    
    /* listen to socket */
    ret = listen(lsocket, 10);
    if (ret == -1) {
	perror("Can't listen to lsocket");
	exit(1);
    }
    

    /* multiplex server loop */
    while (1) {
	
	/* prepare rset and select */
	FD_ZERO(&rset);
	FD_SET(lsocket, &rset);
	maxfd = lsocket;
	for (i=0; i< MAXCON; i++) {
	    if (job_entry[i].socket != -1) {
		FD_SET(job_entry[i].socket, &rset);
		maxfd=(maxfd>job_entry[i].socket?maxfd:job_entry[i].socket);
	    }
	}

	/* select sockets ready for reading */
	ret = select(maxfd+1, &rset, NULL, NULL, NULL);

	/* check for new connections */
	if (FD_ISSET(lsocket, &rset)) {
	    
	    /* accept new connection */
	    cliaddr_len = sizeof(cliaddr);
	    asocket=accept(lsocket, (struct sockaddr*) &cliaddr, &cliaddr_len);
	    if (ret == -1) {
		perror("Can't accept on lsocket");
		exit(1);
	    }
     

	    /* write new job entry */
	    for (i=0; i<MAXCON; i++) {
		if (job_entry[i].socket == -1) {
		    break;
		}
	    }
	    /* no free fd ready */
	    if (i == MAXCON) {
		printf("out of job entries\n");
		close(asocket);
		break;
	    }
	    job_entry[i].socket = asocket;
	    job_entry[i].linec = 1;
	    
	    /* get FILE ptr */
	    job_entry[i].fpin = fdopen(asocket, "r");
	    job_entry[i].fpout = fdopen(asocket, "w");
	    /* set line buffered */
	    setvbuf(job_entry[i].fpin, NULL, _IOLBF, BUFSIZ);
	    setvbuf(job_entry[i].fpout, NULL, _IOLBF, BUFSIZ);

	    /* "log" connection */
	    printf("Connection from %s, port %d\n",
		    inet_ntoa(cliaddr.sin_addr),
		    ntohs(cliaddr.sin_port));
	}
	    
	/* now check for other sockets */
	for (i=0; i<MAXCON; i++) {
	    if (job_entry[i].socket != -1 &&
		FD_ISSET(job_entry[i].socket, &rset)) {

		/* read one line of input */
		if (fgets(line, MAXLINE, job_entry[i].fpin) == NULL) {
		    close(job_entry[i].socket);
		    job_entry[i].socket = -1;
		}
		
		/* write out line */
		fprintf(job_entry[i].fpout, "line %d: %s",
		       	job_entry[i].linec++, line);
	    }
	}

    }
    
    /* when do we close lsocket ? */
    /* propably should catch signal or so */

}
