Per questo progetto è stato richiesto di realizzare un programma che permetta la comunicazione attraverso socket tra processi e thread. Come prima decisione è stata presa quella di organizzare il programma in maniera tale da distinguere attraverso l’utilizzo di una chiamata fork() il mio processo main in due processi: MasterWorker (processo padre) e Collector (processo figlio). La chiamata fork() ritorna infatti un intero (pid_t) che ci permette di riconoscere in quale processo siamo. Nel processo main ho quindi deciso di occuparmi del riconoscimento dei parametri opzionali passati come argomenti (-n –q –t) utilizzando un iteratore (for loop) che confronta i valori, cosi da conoscere anche l’indice di partenza dei file nell’array dei parametri argv[]. Immediatamente inizializzo e dichiaro le strutture per poter mascherare i segnali prima installando un gestore all’handler della struct sigaction, successivamente chiamando la funzione sigaction e dichiaro anche tutto il necessario per i thread: array di thread, struct che i thread utilizzeranno e la coda condivisa controllando che non diano errori e creo il processo nuovo con una fork().
-
PROCESSO PADRE – MASTERWORKER - PID!=0 Qui utilizzando il pid mi distinguo dal processo figlio e inizia l’esecuzione del nostro processo MasterWorker. Creo N thread con la chiamata pthread_create() e successivamente inizio a inserire in coda i file controllando che siano file regolari. Se ricevo un segnale di interruzione smetto di inserire in coda, esco dall’iteratore e inserisco in coda (N-1) segnali EOS (end of stream) per far terminare i thread e 1 segnale “quit” per far terminare l’ultimo thread e il collector in modo da poter svuotare la coda prima, in alternativa, vengono inseriti tutti i file in coda e N segnali EOS di terminazione. Dopo aver inserito i segnali di terminazione aspetto gli N thread con una thread_join, aspetto la terminazione del collector così da non perdermi possibili output con una waitpid() e libero lo spazio di allocazione della coda eliminandola e dei thread e la loro struttura dati con una free(). Successivamente elimino il file socket “farm.sck” con una unlink() ed esco dal processo con successo.
-
PROCESSO FIGLIO – COLLECTOR - PID==0 Qui ci troviamo nel processo figlio duplicato con id uguale a zero. Il figlio con la fork() eredita tutta la gestione dei segnali effettuata dal processo padre. Inizializzo e dichiaro subito le variabili che mi serviranno per lavorare con il socket: i file_descriptor (file_d) per socket, set per segnare i file_d presenti e pronti e la struttura per il server. Copio quindi il nome del socket per il “path” del server, creo il socket con la chiamata socket() che ritorna un intero (file_d), collego il server con il file_d socket appena creato con la chiamata bind() e con una chiamata listen() avverto quanti client possono essere ascoltati dal socket, in questo caso un valore di default SOMAXCONN (4096). Spunto presente nel set il file_d associato al socket e inizio a iterare in un loop while(true) infinito. Ad ogni iterazione copio un altro set rdset con il mio set prima dell’azione della chiamata select() proprio perché quest'ultima si mette in attesa finché non è disponibile un file_d tra tutti i (fd_max+1) specificati e allo sblocco il set rdset viene modificato. La select() impostata in un while verificando l’errno permette di riprendere l’esecuzione se è stata fermata da un'interruzione. Se un file_d è disponibile allora itera da 0 a fd_max+1 cercando uno che è presente nel set con la chiamata FD_ISSET(). Se la richiesta proviene proprio da un client (thread) allora si salva l’intero ritornato dalla chiamata accept() in fd_client e lo segno presente nel set, in questo modo alla prossima iterazione posso leggere il messaggio con la chiamata read() da parte del client senza rischiare di trovarmi in attesa perché il client prima della chiamata write() fa altre operazioni. Creo il buffer buff dove salvo ciò che leggo con read(), se leggo zero caratteri rimuovo il file_d dal set con la FD_CLR(), aggiorno fd_max e se ho letto tutti i file o ho letto il segnale di terminazione tramite interruzione termino con successo e chiudo i file_d senno controllo sempre se devo terminare e in alternativa stampo nello stdout il buffer incrementando il contatore per sapere quando terminare (readf).
-
THREADS (CLIENTS) I thread si occupano di rimuovere concorrentemente uno alla volta gli elementi dalla coda che è contenuta nella struct passata come argomento: se l’elemento rimosso è segnale di terminazione EOS il thread esce dal loop e termina, se è segnale di interruzione “quit” il thread lo comunica al socket tramite connessione e successivamente lo scrive al collector con cui si è connesso con write(), infine se è un file il thread lo apre con la chiamata fopen(), elabora il dato svolgendo i calcoli e lo salva in un buffer stringa. Il thread tenta la connessione con il socket del processo collector (se non riesce la connessione a causa dell'assenza del collector fa una sleep() di un secondo e riprova) e a seguito della connessione scrive il risultato con write() nell’apposito file_d. Con successo chiudo il file_d dell'attuale socket, chiudo il file_d del file aperto in lettura e procedo alla prossima iterazione.