Mi sono deciso a rilasciare il codice corretto e modificato. La decisione di farlo è per tener traccia di una soluzione valida per i posteri
La modifica sostanziale è stata quella di aggiungere un tempo massimo per la select che altrimenti rimane bloccata; ho poi aggiunto tutte (?) le possibili gestioni di errori ed un conteggio di retry da parte del client ( prova per 5 volte la connessione e poi muore). Un'altra modifica logica l'ho fatto sulla funzione aggiorna che ho chiamato test_it, la quale conta il numero di descrizioni presenti ma non aggiorna fd_num e viene controllata in fase di timeout della select. Questo perchè potrebbe essere interessante avere ulteriori clienti che chiamano e se il server è up risponde anche a loro.
Una cosa che non riesco a verificare è come entrare in EOF per il client... io qui semplicemente chiudo dopo il botta e risposta client/server in entrambi i casi.
Ecco il sorgente:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h> //Indirizzi AF_UNIX
#include <unistd.h> //Close
#define TRUE 1
#define UNIX_PATH_MAX 108
#define SOCKNAME "./mysock"
#define NCLIENTS 3
#define TIMEOUT_SEC 3
#define N 100
static void run_server(struct sockaddr_un *sa);
static void run_client(struct sockaddr_un *sa);
int test_it (fd_set *set, int n)
{
int i,count;
for (i=count=0;i<=n;i++)
{
if (FD_ISSET(i,set))
count++;
}
return count!=1 ? TRUE : !TRUE;
}
int main (void)
{
struct sockaddr_un sa; // ind AF_UNIX
int i;
// Riempio la struttura per l'indirizzamento locale
memset (&sa,0,sizeof(sa));
strncpy(sa.sun_path, SOCKNAME, UNIX_PATH_MAX);
sa.sun_family = AF_UNIX;
// Cancello eventuale collegamento precedente
unlink(SOCKNAME);
// Eseguo le fork(s) per i(l) client(s)
for(i=0; i< NCLIENTS; i++)
run_client(&sa);
// Eseguo il server
run_server (&sa);
return 0;
}
static void run_server(struct sockaddr_un *sa)
{
int fd_sk; // socket di connessione
int fd_c; // socket di I/O con un client
int fd_num=0; // max fd attivo
int fd; // indice per verificare risultati select
char buf[N]; // buffer messaggio
fd_set set; // l’insieme dei file descriptor attivi
fd_set rdset; // insieme fd attesi in lettura
int nread; // numero caratteri letti
int nfdselected; // numero descrittori restituiti dalla select
struct timeval tv; // timeout per la select
// Settiamo 10 sec di timeout per la select
tv.tv_sec=TIMEOUT_SEC; tv.tv_usec=0;
// Apro il socket
if ((fd_sk = socket(AF_UNIX,SOCK_STREAM,0))<0)
{
perror ("socket() failed\n");
exit (EXIT_FAILURE);
}
// Assegno l'indirizzo al socket
if (bind(fd_sk,(struct sockaddr *)sa, sizeof(*sa))<0)
{
perror ("bind() failed");
exit (EXIT_FAILURE);
}
// Metto in ascolto il socket
if (listen(fd_sk,SOMAXCONN)<0)
{
perror ("listen failed");
exit (EXIT_FAILURE);
}
// Mantengo il massimo indice di descrittore attivo in fd_num
if (fd_sk > fd_num) fd_num = fd_sk;
// Uso le MASCRO FD_ per settarmi la tabella dei descrittori 'intercettabili'
FD_ZERO(&set); // Azzero
FD_SET(fd_sk, &set); // Setto il descrittore del server
fprintf (stdout,"Server #%d: START with FD #%d\n",getpid(),fd_sk);
while (TRUE)
{
// Preparo i descrittori per la select
// Bisogna inizializzare ogni volta rdset perchè la select lo modifica
rdset = set;
// La select rimane bloccata in attesa di una connessione client
// Attenzione al ‘+ 1’; vogliamo il numero dei descrittori attivi
if ((nfdselected=select(fd_num+1,&rdset,NULL,NULL,&tv))<0)
{
perror ("select() failed");
exit(EXIT_FAILURE);
}
if (nfdselected==0)
{
int retcode=test_it (&set,fd_num);
fprintf (stdout,"Timeot occurred! No data after %d seconds.\n",TIMEOUT_SEC);
fprintf (stdout,"Closing Server with FD #%d.\n",fd_sk);
fprintf (stdout,"Program Exit with %s\n", retcode ? "FAILURE" : "SUCCESS");
close (fd_sk);
exit(retcode);
}
// La select ha intercettato una connessione
// iterando per in numero massimo dei descrittori cerchiamo
// il descrittore che 'chiama'
for (fd = 0; fd <= fd_num; fd++)
{
if (FD_ISSET(fd,&rdset))
{
if (fd == fd_sk)
{
// Se è il descrittore del server proviamo ad accettare
// la connessione
if ((fd_c = accept(fd_sk,NULL,NULL))<0)
{
perror ("accept() failed");
exit(EXIT_FAILURE);
}
fprintf (stdout,"Server #%d: Connection to server accepted! Return FD #%d\n",getpid(),fd_c);
// Andiamo ad aggiungere nella nostra maschera il descrittore del client
// ...ed eventualmente incrementiamo il numero massimo di descrittori
FD_SET(fd_c, &set);
if (fd_c > fd_num) fd_num = fd_c;
}
else
{
// sock I/0 pronto
nread = read(fd, buf, N);
if (nread<=0)
{
// EOF client finito
fprintf (stdout,"Server #%d: EOF on client\n",getpid());
}
else
{
// Risposta dal Server
fprintf(stdout,"Server #%d: got \"%s\" (%d byte) from FD #%d\n",getpid(),buf,nread,fd) ;
write(fd,"Bye!",5);
}
// Togliamo il descrittore dalla maschera e chiudiamo il socket
FD_CLR(fd,&set);
close(fd);
fprintf(stdout,"Server #%d: CLOSE FD #%d\n",getpid(),fd);
}
}
}
}
}
static void run_client(struct sockaddr_un *sa)
{
pid_t pid;
int fd_client,nread;
// La socket l'andiamo a fare a livello parent in quanto i descrittori
// vengono trasmessi ai figli automaticamente.
// Non mi risulta valido mettere la socket all'interno della fork in quanto
// verrebbero rilasciato identici descrittori per tutti i figli
if ((fd_client = socket(AF_UNIX,SOCK_STREAM,0))<0)
{
perror ("socket() failed");
exit (EXIT_FAILURE);
}
pid=fork();
if (pid == 0)
{
int rc,retry=0;
char buf[N];
fprintf (stdout,"Client #%d: START with FD #%d\n",getpid(),fd_client);
do
{
rc = connect(fd_client, (struct sockaddr *)sa, SUN_LEN(sa));
if (rc < 0)
{
fprintf(stderr,"Client #%d: connect failed: %s. Retrying %d/%d\n",getpid(),strerror(errno),++retry,5);
if (retry==5)
{
perror ("connect() failed");
exit (EXIT_FAILURE);
}
sleep (1);
}
}while (rc < 0);
write(fd_client,"Hallo!",7);
nread=read(fd_client,buf,N);
fprintf(stdout,"Client #%d: got \"%s\" (%d byte) from Server #%d\n",getpid(),buf,nread,getppid()) ;
fprintf(stdout,"Client #%d: CLOSE FD #%d && die\n",getpid(),fd_client);
close(fd_client);
exit(EXIT_SUCCESS);
}
}
Questo è l'output:
max@studio:~/test> ./forum
Client #6110: START with FD #3
Client #6111: START with FD #4
Client #6110: connect failed: No such file or directory. Retrying 1/5
Client #6111: connect failed: No such file or directory. Retrying 1/5
Server #6109: START with FD #6
Client #6112: START with FD #5
Server #6109: Connection to server accepted! Return FD #7
Server #6109: got "Hallo!" (7 byte) from FD #7
Server #6109: CLOSE FD #7
Client #6112: got "Bye!" (5 byte) from Server #6109
Client #6112: CLOSE FD #5 && die
Server #6109: Connection to server accepted! Return FD #7
Server #6109: Connection to server accepted! Return FD #9
Server #6109: got "Hallo!" (7 byte) from FD #7
Client #6110: got "Bye!" (5 byte) from Server #6109
Client #6110: CLOSE FD #3 && die
Server #6109: CLOSE FD #7
Server #6109: got "Hallo!" (7 byte) from FD #9
Server #6109: CLOSE FD #9
Client #6111: got "Bye!" (5 byte) from Server #6109
Client #6111: CLOSE FD #4 && die
Timeot occurred! No data after 3 seconds.
Closing Server with FD #6.
Program Exit with SUCCESS
max@studio:~/test>