/* Programma:     servente_chat
*  Autore:        FF
*  Data:          08/02/2006
*  Descr.:        Esempio di servente di chat che riceve connessioni 
*                 e le gestisce con la funzione select (multiplexing) invece 
*                 di creare un figlio per ogni connessione; tutto quello che
*                 inviato da un cliente viene rispedito dal servente
*                 a tutti gli altri clienti
*/
#include "utilsock.h"
/* utilsock.h contiene tutte le inclusioni delle altre librerie necessarie
*  e le funzioni di lettura e scrittura dal socket
*/
#include <sys/select.h>
#include <netdb.h>
#define MAX        256
#define BACKLOG 20

int main(int argc, char *argv[])
{
    fd_set pri;                     // pri lista di descrittori per select()
    fd_set set_lettura;             // temp altra lista di descrittori
    struct sockaddr_in indserv;     // indirizzo del servente
    struct sockaddr_in indcli;      // indirizzo di un cliente
    int sdgr;                       // numero descrittore piu' alto
    int sd;                         // socket
    int csd;                        // socket connesso con accept()
    char buf[MAX],buf2[MAX];        // buffer dei dati
    int bytel;
    in_port_t porta;
    int val=1;                      // per la setsockopt()
    socklen_t lind;
    struct hostent *cli;            // per trovare nome cliente dall'IP
    struct in_addr vet[MAX];        // per mem. IP cliente (max 256 clienti)        
    int i, j;
    printf("\033[2J\033[H");
    //controlli sui parametri
    if (argc!=2) {
        printf("Digitare ./servente_chat porta\n");        
        exit(-1);
    }
    porta=atoi(argv[1]);    
    FD_ZERO(&pri);                    // pulizia liste descrittori
    FD_ZERO(&set_lettura);
    // socket
    if ((sd = socket(PF_INET, SOCK_STREAM, 0))<0) {
        perror("Errore nella socket");
        exit(-1);
    }
    // rende l'indirizzo riutilizzabile
    if (setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(int))<0) {
        perror("Errore nella setsockopt");
        exit(-1);
    }
    // bind
    indserv.sin_family = AF_INET;
    indserv.sin_addr.s_addr = INADDR_ANY;
    indserv.sin_port = htons(porta);
    memset(&(indserv.sin_zero), '\0', 8);
    if (bind(sd, (struct sockaddr *)&indserv, sizeof(indserv)) == -1) {
        perror("Errore nella bind");
        exit(-1);
    }
    // listen
    if (listen(sd,BACKLOG)<0) {
        perror("Errore nella listen");
        exit(-1);
    }
    printf("servente_chat pronto; 'Ctrl+c' per chiuderlo\n\n");
    // aggiunge sd al set principale
    FD_SET(sd, &pri);
    // conserva il descrittore di file piu' grande
    sdgr = sd; 
    /*
    * cuore del programma:
    * prima della select copia la lista pri (che contiene tutti i socket 
    * aperti e quello tuttora in ascolto) in set_lettura in modo che pri 
    * non venga "sporcato" dalla select; set_lettura è usato solo nella select 
    * e nel successivo test per rilevare socket attivi; eventuali nuovi
    * socket connessi vengono aggiunti a pri cosi' come i socket chiusi
    * vengono rimossi da pri
    */
    while(1) {
        set_lettura = pri;                 // imposta set per la select()
        if (select(sdgr+1, &set_lettura, NULL, NULL, NULL)<0) {
            perror("Errore nella select");
            exit(-1);
        }
        // ispeziona le connessioni per cercare dati in arrivo
        for(i=0;i<=sdgr;i++) {
            if (FD_ISSET(i, &set_lettura)) {     // trovato un socket con dati
                if (i==sd) {
                    lind = sizeof(indcli);
                    if ((csd=accept(sd,(struct sockaddr *)&indcli,&lind))<0) { 
                        perror("Errore nella accept");
                    }
                    else {
                        FD_SET(csd, &pri);   // aggiunge socket conn al set pri
                        if (csd > sdgr) {    
                            sdgr = csd;
                        }
                        printf("servente_chat: connessione da %s sul "
                            "socket %d\n",inet_ntoa(indcli.sin_addr),csd);    
                        vet[csd]=indcli.sin_addr;         // conserva IP cliente
                    }
                }
                else {
                    // gestisce dati in arrivo da un cliente                    
                    if ((bytel=leggisock(i,buf,sizeof(buf)))<= 0) {

                        if (bytel==0) {
                            // connessione chiusa dal cliente
                            printf("servente_chat: socket %d chiuso\n", i);
                        }
                        else {
                            perror("Errore in ricezione");
                        }
                        close(i); 
                        FD_CLR(i,&pri);     // toglie il socket da pri
                    }
                    else {    
                        // invia i dati ai vari clienti
                        // prima recupera IP del mittente e ne trova il nome
                        // grazie a IP salvato in vet[i]; mette il nome
                        // in buf2 in testa ai dati da inviare
                        strcpy(buf2,"Da: ");
                        cli=gethostbyaddr((char *)&vet[i],4,AF_INET);
                        if (cli!=NULL) {
                            strcat(buf2,cli->h_name);                            
                        }
                        else {
                            strcat(buf2,inet_ntoa(vet[i]));                            
                        }    
                        strcat(buf2," ----> ");
                        strcat(buf2,buf);    
                        for(j=0;j<=sdgr;j++) {
                            if (FD_ISSET(j, &pri)) {
                                // non invia i dati al mittente
                                if (j!=sd && j!=i) {
                                    if (scrivisock(j,buf2,bytel)<0) {
                                          perror("Errore in spedizione");
                                    }
                                }
                            }             // fine if (FD_ISSET(j.....))
                        }                // fine for j<=sdgr
                    }                    // fine else: invia i dati
                }                        // fine else: gestisce dati in arrivo 
            }                            // fine if (FD_ISSET(i.....))
        }                                // fine for i<=sdgr
    }                                    // fine while(1)
    return 0;
}

