La struttura del server è classica:
Vediamo di analizzare in dettaglio le diverse sezioni commentando i sorgenti originali.
#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
).
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
).
/* * 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.
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:
scandir()
, delle rispettive
common_dir
, selezionando i file da includere nella lista
tramite select_dir
ed
ordinandoli tramite la funzione standard aplhasort()
.
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».
mtime
):
send_file
);client.c
).