=== Server multi-thread con pool di thread preallocati === Si realizzi un server concorrente che utilizza un pool di thread gestori per servire i propri client. Il server implementa un semplice servizio di eco: il client invia al server un messaggio contenente una singola parola, ed il server rimanda lo stesso messaggio indietro al client. Il client si limita a stampare il contenuto del messaggio di risposta ricevuto dal server. Il programma client accetta tre parametri da linea di comando: - L'indirizzo IP del server remoto - Il numero di porta TCP del server remoto - Una stringa che rappresenta la parola da inviare al server Il programma server attende connessioni provenienti da qualsiasi interfaccia di rete, ed accetta un solo parametro da linea di comando: - La porta TCP su cui mettersi in ascolto Il thread main alloca un pool di NUM_THREAD thread gestori prima di accettare nuove connessioni. Dopodiche' rimane perennemente in attesa di nuove connessioni, assegnando ciascuna connessione ad uno dei thread gestori che risulta in quel momento libero. La costante NUM_THREAD e' nota a tempo di compilazione e non varia durante l'esecuzione del programma. Il workflow del server puo' pertanto essere schematizzato nel seguente modo: 1. il thread main crea NUM_THREAD thread gestori; 2. il thread main si pone in attesa di connessioni in ingresso; 3. quando arriva una connessione in ingresso: b. se tutti i gestori nel pool sono tutti occupati, il thread main si blocca in attesa che se ne liberi uno qualsiasi; c. se c'e' almeno un thread gestore libero, il thread main sceglie uno di questi e lo attiva in modo tale che gestisca la nuova connessione; 4. ciascun thread gestore ripete all'infinito le seguenti operazioni: a. si blocca aspettando che gli venga assegnata una connessione; b. quando una connessione arriva, riceve la parola dal client; c. spedisce la parola indietro al client; d. chiude la connessione e. segnala al thread main di essere nuovamente libero; ==== Suggerimenti ==== 1. Considerate un array di semafori evento, uno per ciascun thread del pool: ogni thread gestore puo' bloccarsi sul suo semaforo evento in attesa di essere svegliato dal thread main al sopraggiungere di una nuova richiesta assegnata al gestore; 2. Considerate un array di NUM_THREAD interi, in cui l'intero i-esimo indica se il thread i-esimo e' occupato o libero: il thread main puo' scorrere questa struttura dati per individuare i thread attualmente occupati. Quando una richiesta viene assegnata ad un thread gestore libero, tale thread viene registrato come occupato; una volta servita la richiesta, il thread gestore si puo' registrare come libero. 3. Considerate un array di NUM_THREAD interi in cui l'intero i-esimo contiene il file descriptor del socket associato alla connessione gestita in quel momento dal thread i-esimo. In pratica, quando il thread main accetta una richiesta, memorizza in questo array il file descriptor del socket di connessione, nella posizione corrispondente al thread gestore selezionato. 4. Se tutti i thread gestori sono occupati, il thread main deve attendere che un thread gestore si liberi. E' pertanto necessario un meccanismo di sincronizzazione (un semaforo o una variabile condition) che permetta al thread main di bloccarsi quando non ci sono thread liberi. 5. Ogni risorsa condivisa (acceduta in lettura e/o scrittura da piu' thread) deve essere protetta da opportuni lock. 6. Per una soluzione piu' elegante, si puo' utilizzare un solo array di NUM_THREAD elementi, in cui ciascun elemento e' una struttura contenente tutti i dati necessari per un thread gestore: il suo identificatore pthread, un semaforo evento, un campo per indicare se il thread e' libero o occupato ed un campo per memorizzare il file descriptor del client corrente.