Socket af_unix, uso della select

di il
6 risposte

Socket af_unix, uso della select

Lo so, lo so... vi sto sfruttando troppo
ma siete stati così bravi che mi sono affezionato
cmq in questi giorni cercherò di dare una mano al forum per sdebitarmi.

Ho appena iniziato l'argomento della gestione dei socket attraverso la select.
Ho preso un esempio fatto dalla nostra prof. ma non funziona (Lei ci mette parecchi giorni a rispondere alle e-mail che gli mando... dato che non ho molto tempo confido in voi).
Allora mi sono scoraggiato, perché non riesco a capire un nuovo argomento se non ho un esempio a cui fare affidamento. Ho provato anche a cercare su google... ma trovo esempi troppo complessi e già con l'uso dei socket af_inet.

L'esempio della prof è il seguente:

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

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h> /*Indirizzi AF_UNIX*/

#define TRUE 1
#define UNIX_PATH_MAX 108
#define SOCKNAME "./mysock"
#define N 100

static void run_server(struct sockaddr_un *sa);
static void run_client(struct sockaddr_un *sa);

int main (void){
	int i; 
	struct sockaddr_un sa; /* ind AF_UNIX */

	strncpy(sa.sun_path, SOCKNAME, UNIX_PATH_MAX);
	sa.sun_family = AF_UNIX;

	for(i=0; i< 4; i++)
		run_client(&sa); /* attiv client*/

	run_server (&sa); /* attiv server */

	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 */
	
	fd_sk = socket(AF_UNIX,SOCK_STREAM,0);
	bind(fd_sk,(struct sockaddr *)sa, sizeof(*sa));
	listen(fd_sk,SOMAXCONN);
	
	if (fd_sk > fd_num) fd_num = fd_sk; /* mantengo il massimo indice di descrittore attivo in fd_num */
	FD_ZERO(&set);
	FD_SET(fd_sk, &set);

	while (TRUE) {
		/* preparo maschera per select */
		rdset = set; /*Bisogna inizializzare ogni volta rdset perchè la select lo modifica*/

		if ( select(fd_num+1,&rdset,NULL,NULL,NULL) == -1 ){ fprintf(stderr,"Error Select!\n"); exit(EXIT_FAILURE); }
		/*Attenzione al ‘+ 1’,FD_SET(fd_sk,&set); vogliamo il numero dei descrittori attivi*/
		else { /* select OK */
			for (fd = 0; fd < fd_num; fd++) {
				
					if (FD_ISSET(fd,&rdset)) {
						if (fd == fd_sk) {/* sock connect pronto */
							fd_c = accept(fd_sk,NULL,0);
							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 */
								FD_CLR(fd,&set);
								/*qui c'è una funzione aggiorna non definita... non riesco a capire cosa volesse fare*/
								fd_num = aggiorna(&set);
								close(fd); 
							}
							else { /* nread !=0 */
								printf("Server got: %s\n",buf) ;
								write(fd,"Bye!",5);
							}
						} 
					} 
			}
		}
	}
}

static void run_client(struct sockaddr_un *sa) {
	if (fork() == 0) { /* figlio, client */
		int fd_client; char buf[N];
		fd_client = socket(AF_UNIX,SOCK_STREAM,0);
		
		while ( connect(fd_client, (struct sockaddr*)sa, sizeof(*sa)) == -1 ) {
			if ( errno == ENOENT ) sleep(1);
			else{ fprintf(stderr,"Error Client!\n"); exit(EXIT_FAILURE); }
		}

		write(fd_client,"Hallo!",7);
		read(fd_client,buf,N);
		printf("Client got: %s\n",buf) ;
		close(fd_client);
		exit(EXIT_SUCCESS);
	}
}
dato che è un nuovo argomento mi risulta difficile capire come aggiustarlo.
Mi chiedevo se mi potesse dare una mano.

Grazie!

6 Risposte

  • Re: Socket af_unix, uso della select

    https://lists.linux-foundation.org/pipermail/lsb-bugs/2011-January/014486.html
    
       memset (&sa,0,sizeof(sa));//$$max
    
    EDIT:
    Cavolata immonda! UNIX_PATH_MAX è definito nel programma
    memset non ha ragione di esistere in quanto la struttura viene completamente riscritta
  • Re: Socket af_unix, uso della select

    Connessione rifiutata dal client. Il Server funziona
    
    max@studio:~> gcc forum.c -o forum
    max@studio:~> ./forum &
    [1] 8063
    max@studio:~> netstat -p | grep forum
    (Not all processes could be identified, non-owned process info
     will not be shown, you would have to be root to see it all.)
    unix  2      [ ]         STREAM                   100421 8063/forum
    max@studio:~> kill 8063
    [1]+  Terminated              ./forum
    max@studio:~>
    
    
  • Re: Socket af_unix, uso della select

    A me risulta che I descrittori dei socket aperti dal padre prima delle fork vengono condivisi coi figli... ma questo non accade nella funzione run_client il quale continua a rilasciare lo stesso ID descrittore causando un errore sul BUS.... è quì mi va in palla la select evidentemente.Per cui, secondo me, la funzione socket del client deve essere chiamata dal padre e non dai figli.

    Un piccolo errore è presente sul lato server, dove itera da zero a < fd_num senza tener presente che dobbiamo aggiungerne 1... patchiamo aggiungendo un uguale dopo il minore.

    La funzione aggiorna è stata pensata per aggiornare il numero massimo di descrittori attivi, qualora qualche client chiudesse la connessione... ma sinceramente mi dovrei informare in quanto non mi sembra sia una semplice bit-table... credo ci sia dietro una struttura ...

    Io l'ho reso funzionante, ma la parte sulla fork da debuggare è stata un casino in quanto non solo è impossibile con debug, ma per ogni cannata devi 'killare' i processi zombi vaganti... va bè...
    Non mi è piaciuto la mancanza di gestione degli errori sulla bind e sulla listen che se ci fossero stati mi avrebbero fatto risparmiare un pò di tempo.

    Per il resto il codice è piaciuto. Non sapevo della possibilità di connessione AF_UNIX tramite BUS senza usare la INET su TCP/IP.... ovviamente limitandoti alla stessa macchina!

    Saluti,
    Max
  • Re: Socket af_unix, uso della select

    Grande ixamit... ultimamente mi stai aiutando un botto.
    Però le tue conoscenze sono troppo avanti.
    Ci sono stato un'oretta a cercare di comprendere le tue risposte, senza molto successo XD... sono troppo ignorante su questi argomenti
    Ad esempio:
    A me risulta che I descrittori dei socket aperti dal padre prima delle fork vengono condivisi coi figli... ma questo non accade nella funzione run_client il quale continua a rilasciare lo stesso ID descrittore causando un errore sul BUS.... è quì mi va in palla la select evidentemente.Per cui, secondo me, la funzione socket del client deve essere chiamata dal padre e non dai figli.
    Cmq hai ragione.. con il minore e uguale adesso funziona.
    Il problema è, come dici tu, che i processi rimangono <defunti>, inoltre a me il programma non termina.

    Mi posteresti la tua versione? please!!!!!

    Cmq sono contento di averti svelato, con questo esempio, una cosa che non sapevi .
  • Re: Socket af_unix, uso della select

    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>
    
  • Re: Socket af_unix, uso della select

    Grazie!!!
    Sei veramente un mito!

    So che è stato difficile mettere qui il tuo codice, ma sappi che ne farò buon uso e che mi hai aiutato, ancora una volta, tantissimo.

    A presto
Devi accedere o registrarti per scrivere nel forum
6 risposte