Precedente Successivo

I sorgenti: server.c

La struttura del server è classica:

  • lettura della riga di comando ed inizializzazione del socket d'ascolto;
  • attesa di una connessione e forking;
  • dialogo con il client.
  • Vediamo di analizzare in dettaglio le diverse sezioni commentando i sorgenti originali.

    Inizializzazione


    #include <stdio.h>		/* printf(), perror() */
    #include <unistd.h>		/* read(), write(), chdir(), close(),
    				   gethostname(), fork() */
    #include <sys/socket.h>		/* socket(), connect(), listen(), accept() */
    #include <sys/stat.h>		/* stat() */
    #include <arpa/inet.h>		/* inet_ntoa(), ntohs(), htons() */
    #include <sys/dir.h>		/* scandir(), alphasort() */
    #include <stdlib.h>		/* malloc() */
    #include <fcntl.h>		/* open() */
    
    #include "defs.h"
    #define MAX_CONNECTIONS 3
    #define DEBUG
    
    extern int select_dir(struct direct *);
    extern int readmsg(const int, char *, const size_t);
    extern int writemsg(const int, const  char *, const size_t);
    extern int readstat(const int, const struct stat *);
    extern int writestat(const int, const struct stat *);
    void send_file(const int, const char *);
    int updatedir(const int, const char *);
    
    const char *PRGNAME;
    
    void
    main(argc, argv)
    	int argc;
    	char *argv[];
    {
    	int sock_server;
    	char common_dir[1024];
    	struct sockaddr_in server;
    	pid_t childpid;
    #ifdef DEBUG
    	char hostname[8];
    	size_t hostlen = sizeof(hostname);
    #endif DEBUG
    
    	PRGNAME = argv[0];
    
    	if (argc == 2)
    		strcpy(common_dir, argv[1]);
    	else
    		strcpy(common_dir, ".");
    
    	/*
    	 * Makes a socket for the server
    	 */
    	sock_server = socket(AF_INET, SOCK_STREAM, 0);
    	if (sock_server < 0) {
    		perror("opening server socket");
    		exit(1);
    	}
    
    	/*
    	 * Binds socket to the server
    	 */
    	server.sin_family = AF_INET;
    	server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    	server.sin_port = htons(SERVER_PORT);
    	if (bind(sock_server, (struct sockaddr *)&server, sizeof(server))) {
    		perror("binding server socket");
    		exit(1);
    	}
    
    	listen(sock_server, MAX_CONNECTIONS);
    #ifdef DEBUG
    	if (gethostname(hostname, hostlen))
    	{
    		perror("getting hostname");
    		exit(1);
    	}
    	printf("%s: started\n", PRGNAME);
    	printf("\thost: %s [%s]\n", hostname, inet_ntoa(server.sin_addr));
    	printf("\tprot: %d\n", ntohs(server.sin_port));
    #endif
    

    Nella prima parte del listato viene letta la riga di comando per impostare la stringa common_dir: se è passato un argomento, questo indicherà la directory di cui effettuare il mirroring sul lato server.

    Successivamente viene chiesta la creazione di un socket nel dominio ARPA Internet protocols di tipo stream. Se anche l'assegnazione di un nome al socket ha successo (bind), il server crea una coda di MAX_CONNECTIONS connessioni (listen).

    Attesa di una connessione


    	do {
    		/*
    		 * Waits for a new connection and, when it arrives,
    		 * forks
    		 */
    		int sock_client, client_len;
    		struct sockaddr_in client;
    
    		client_len = sizeof(client);
    		sock_client = accept(sock_server, (struct sockaddr *)&client, &client_len);
    		if (sock_client < 0 )
    		{
    			perror("accept");
    			continue;
    		}
    		if ((childpid = fork()) == -1)
    		{
    			perror("forking");
    			continue;
    		}
    		if (childpid > 0)
    		{
    			close(sock_client);
    			continue;
    		}
    		close(sock_server);
    #ifdef DEBUG
    		printf("%s: connected\n", PRGNAME);
    		printf("\tclient: %s\n", inet_ntoa(client.sin_addr));
    		printf("\tport: %u\n", ntohs(client.sin_port));
    #endif
    		
    		/*
    		 * Updates local common directory recursively.
    		 */
    		if (updatedir(sock_client, common_dir) == FALSE)
    			fprintf(stderr, "updatedir: Fatal error\n");
    
    		close(sock_client);
    	} while (childpid != 0);
    
    	close(sock_server);
    }
    

    In questo ciclo "do... while", che per il processo padre non avrà mai termine, il server attende una nuova connessione sul socket sock_client (accept) che, una volta stabilita, provocherà un fork: il processo padre si rimetterà in ascolto, dopo aver chiuso il socket con il client, per un'altra connessione, mentre il processo figlio, dopo aver chiuso il socket delle nuove connessioni (sock_server), inizierà a dialogare con il client per l'aggiornamento della directory (updatedir).

    Sottofunzioni

    Invio del file


    /*
     * Subfunctions
     */
    
    void
    send_file(const int sock, const char *name)
    {
    	int fd, byread, bysent = 0;
    	char buf[BUF_SIZE];
    
    	/*
    	 * Sends file to the client
    	 */
    #ifdef DEBUG
    	printf("%s: Send", PRGNAME);
    #endif
    	fd = open(name, O_RDONLY);
    	for (byread = read(fd, buf, BUF_SIZE); byread > 0;
    			byread = read(fd, buf, BUF_SIZE))
    		bysent += write(sock, buf, byread);
    	printf(" %u bytes data file\n", bysent);
    	close(fd);
    }
    

    Questa semplice subroutine non fa altro che inviare il contenuto del file specificato da "name" sul socket passato come argomento. L'unico fattore da tener presente è il terzo parametro della funzione write() che deve essere impostato all'esatto numero di byte che si vogliono inviare. Il socket, infatti, è di tipo stream e si rischia l'accodamento di byte indesiderati alla fine del file inviato.

    Dialogo con il client

    Questa funzione rappresenta il cuore del server. Il suo scopo consiste nel rispondere alle richieste del client in maniera tale da aggiornarne la directory.


    int
    updatedir(const int sock_client, const char *dir)
    {
    	int totfile, thisfile;
    	char msg[NAME_MAX+1];
    	struct direct **namelist;
    
    	/*
    	 * Scans this local directory
    	 * ordering files alphabetically.
    	 */
    #ifdef DEBUG
    	printf("%s: Scanning dir \"%s\"\n", PRGNAME, dir);
    #endif
    	if (chdir(dir) == -1)
    	{
    		perror("cd'ing");
    		exit(1);
    	}
    
    	if ((totfile = scandir(".", &namelist,
    					select_dir, alphasort)) == -1)
    	{
    		perror("Scanning dir");
    		return FALSE;
    	}
    #ifdef DEBUG
    	printf("%s: %d files found.\n", PRGNAME, totfile);
    #endif
    
    	/*
    	 * Updates directory until both server's and client's
    	 * lists are empty.
    	 */
    	for (readmsg(sock_client, msg, LEN_MESGS),
    			thisfile = 0;
    			thisfile < totfile;
    			readmsg(sock_client, msg, LEN_MESGS))
    	{
    		char *remotename = malloc(NAME_MAX+1);
    		struct stat localstat;
    
    		stat(namelist[thisfile]->d_name, &localstat);
    		if (strcmp(msg, END_LIST) != 0)
    			readmsg(sock_client, remotename, NAME_MAX+1);
    		
    		if (strcmp(msg, END_LIST) != 0
    				&& strcmp(namelist[thisfile]->d_name,
    					remotename) == 0)
    		{
    			/*
    			 * Client has another entry which name is equal
    			 * to that on the server.
    			 */
    			if (S_ISREG(localstat.st_mode))
    			{
    				if (strcmp(msg, ANOTHER_FILE) == 0)
    				{
    					struct stat remotestat;
    
    					/*
    					 * Both server and client dir
    					 * entries are files. Check
    					 * modification time.
    					 */
    					writemsg(sock_client, STAT, LEN_MESGS);
    					readstat(sock_client, &remotestat);
    					if (localstat.st_mtime
    							>= remotestat.st_mtime)
    						writemsg(sock_client,
    								FILE_UPDATED,
    								LEN_MESGS);
    					else
    					{
    						writemsg(sock_client,
    								NEWER_FILE,
    								LEN_MESGS);
    						writestat(sock_client,
    								&localstat);
    						send_file(sock_client,
    								namelist[thisfile]->d_name);
    					}
    					thisfile++;
    				}
    				else
    					/*
    					 * Same name, but client's entry
    					 * is a dir.
    					 */
    					writemsg(sock_client, DIR_REMOVED,
    							LEN_MESGS);
    			}
    			else
    			{
    				/*
    				 * Dir on server's side.
    				 */
    				if (strcmp(msg, ANOTHER_FILE) == 0)
    					writemsg(sock_client, FILE_REMOVED,
    							LEN_MESGS);
    				else
    				{
    					writemsg(sock_client, ENTER_DIR,
    							LEN_MESGS);
    					updatedir(sock_client,
    							namelist[thisfile]->d_name);
    					thisfile++;
    				}
    			}
    		}
    		else if (strcmp(namelist[thisfile]->d_name, remotename) < 0
    				|| strcmp(msg, END_LIST) == 0)
    		{
    			/*
    			 * Server's entry comes before the client's one.
    			 */
    			if (S_ISREG(localstat.st_mode))
    			{
    				writemsg(sock_client, NEW_FILE, LEN_MESGS);
    				writemsg(sock_client,
    						namelist[thisfile]->d_name,
    						sizeof(namelist[thisfile]->d_name));
    				writestat(sock_client, &localstat);
    				send_file(sock_client,
    						namelist[thisfile]->d_name);
    			}
    			else
    			{
    				writemsg(sock_client, NEW_DIR, LEN_MESGS);
    				writemsg(sock_client,
    						namelist[thisfile]->d_name,
    						sizeof(namelist[thisfile]->d_name));
    				writestat(sock_client, &localstat);
    				updatedir(sock_client,
    						namelist[thisfile]->d_name);
    			}
    			
    			thisfile++;
    		}
    		else
    		{
    			/*
    			 * Hier server's entry come after the client's one.
    			 */
    			if (strcmp(msg, ANOTHER_FILE) == 0)
    				writemsg(sock_client, FILE_REMOVED, LEN_MESGS);
    			else
    				writemsg(sock_client, DIR_REMOVED, LEN_MESGS);
    		}
    	}
    
    	writemsg(sock_client, END_LIST, LEN_MESGS);
    	chdir("..");
    	return TRUE;
    }
    

    Vediamo di commentare il sorgente seguendolo passo passo:

    1. A connessione avvenuta, server e client eseguono una scansione, tramite la funzione standard scandir(), delle rispettive common_dir, selezionando i file da includere nella lista tramite select_dir ed ordinandoli tramite la funzione standard aplhasort().
    2. Il client, se la sua lista non è vuota, invia il messaggio «Aother file» o «Aother dir», a seconda che l'elemento della lista namelist puntato correntemente da thisfile sia rispettivamente un file regolare o una sottodirectory, ed a seguire il nome del file. Se la lista è vuota invia la richiesta di chiusura «End of list».
    3. Ricevuto il messaggio ed eventualmente il nome del file, il server deve prendere le sequenti decisioni:
      1. Se il server non riceve il messaggio di «End of list», cioè la sua lista non è ancora vuota, e dal confronto fra il nome sul server e quello sul client risulta che essi coincidono, allora:
        1. se il file sul server è un file regolare, allora è possibile che
          1. sul client si stia considerando un file regolare; è necessario, allora, passare al confronto del tempo dell'ultima modifica (mtime):
            1. se il file sul server è antecedente o ha la stessa data del file sul client, il file non necessita di aggiornamenti;
            2. altrimenti il file va aggiornato.
          2. Oppure è possibile che quella in remoto sia una directory e va quindi cancellata in quanto ora sul server c'è un file regolare;
        2. Se, invece, sul lato server è presente una directory
          1. mentre sul client c'è un file, allora questo va cancellato.
          2. Se invece è una directory bisogna aggiornarla, processandola ricorsivamente («Enter dir»).
      2. In alternativa può accadere che i nomi non coincidano, ovvero che il nome sul server sia alfabeticamente antecedente a quello sul client. Questa condizione comprende il caso estremo in cui il client sia giunto al termine della sua lista, mentre il server possiede ancora dei nomi da processare. In ogni caso un nuovo file deve essere creato sul lato client.
        1. Se il file da creare è di tipo regolare, è sufficiente trasferirne il contenuto e sistemarene i permessi e la data dell'ultima modifica (send_file);
        2. se, invece, si tratta di una directoru, allora, dopo averla creata, è necessario anche visitarla recursivamente.
      3. l'ultima alternativa prevede che l'elemento corrente presente nella lista del server sia identificato da un nome che alfabeticamente succede a quello del lato client. In questo caso il file, regolare o directory che sia, va rimosso dalla directory del client.
    4. Il ciclo ha termine quando il server ha esaminato tutti i file presenti nella sua lista. In questo caso invia un semplice «End of list» ed esce. Per inciso, se al client rimangono altri nomi da processare, significa che questi sono tutti da cancellare (si veda il listato di client.c).


    Precedente Successivo