Come accennato anche in sez. 3.1 una delle modalità più comuni di funzionamento da parte dei server è quella di usare la funzione fork per creare, ad ogni richiesta da parte di un client, un processo figlio che si incarichi della gestione della comunicazione. Si è allora riscritto il server daytime dell'esempio precedente in forma concorrente, inserendo anche una opzione per la stampa degli indirizzi delle connessioni ricevute.
In fig. 15.10 è mostrato un estratto del codice, in cui si sono tralasciati il trattamento delle opzioni e le parti rimaste invariate rispetto al precedente esempio (cioè tutta la parte riguardante l'apertura passiva del socket). Al solito il sorgente completo del server, nel file TCP_cunc_daytimed.c, è allegato insieme ai sorgenti degli altri esempi.
Stavolta (21–26) la funzione accept è chiamata fornendo una struttura di indirizzi in cui saranno ritornati l'indirizzo IP e la porta da cui il client effettua la connessione, che in un secondo tempo, (40–44), se il logging è abilitato, stamperemo sullo standard output.
Quando accept ritorna il server chiama la funzione fork (27–31) per creare il processo figlio che effettuerà (32–46) tutte le operazioni relative a quella connessione, mentre il padre proseguirà l'esecuzione del ciclo principale in attesa di ulteriori connessioni.
Si noti come il figlio operi solo sul socket connesso, chiudendo immediatamente (33) il socket list_fd; mentre il padre continua ad operare solo sul socket in ascolto chiudendo (48) conn_fd al ritorno dalla fork. Per quanto abbiamo detto in sez. 15.2.6 nessuna delle due chiamate a close causa l'innesco della sequenza di chiusura perché il numero di riferimenti al file descriptor non si è annullato.
Infatti subito dopo la creazione del socket list_fd ha una referenza, e lo stesso vale per conn_fd dopo il ritorno di accept, ma dopo la fork i descrittori vengono duplicati nel padre e nel figlio per cui entrambi i socket si trovano con due referenze. Questo fa si che quando il padre chiude sock_fd esso resta con una referenza da parte del figlio, e sarà definitivamente chiuso solo quando quest'ultimo, dopo aver completato le sue operazioni, chiamerà (45) la funzione close.
In realtà per il figlio non sarebbe necessaria nessuna chiamata a close, in quanto con la exit finale (45) tutti i file descriptor, quindi anche quelli associati ai socket, vengono automaticamente chiusi. Tuttavia si è preferito effettuare esplicitamente le chiusure per avere una maggiore chiarezza del codice, e per evitare eventuali errori, prevenendo ad esempio un uso involontario del listening descriptor.
Si noti invece come sia essenziale che il padre chiuda ogni volta il socket connesso dopo la fork; se così non fosse nessuno di questi socket sarebbe effettivamente chiuso dato che alla chiusura da parte del figlio resterebbe ancora un riferimento nel padre. Si avrebbero così due effetti: il padre potrebbe esaurire i descrittori disponibili (che sono un numero limitato per ogni processo) e soprattutto nessuna delle connessioni con i client verrebbe chiusa.
Come per ogni server iterativo il lavoro di risposta viene eseguito interamente dal processo figlio. Questo si incarica (34) di chiamare time per leggere il tempo corrente, e di stamparlo (35) sulla stringa contenuta in buffer con l'uso di snprintf e ctime. Poi la stringa viene scritta (36–39) sul socket, controllando che non ci siano errori. Anche in questo caso si è evitato il ricorso a FullWrite in quanto la stringa è estremamente breve e verrà senz'altro scritta in un singolo segmento.
Inoltre nel caso sia stato abilitato il logging delle connessioni, si provvede anche (40–43) a stampare sullo standard output l'indirizzo e la porta da cui il client ha effettuato la connessione, usando i valori contenuti nelle strutture restituite da accept, eseguendo le opportune conversioni con inet_ntop e atohs.
Ancora una volta l'esempio è estremamente semplificato, si noti come di nuovo non si sia gestita né la terminazione del processo né il suo uso come demone, che tra l'altro sarebbe stato incompatibile con l'uso della opzione di logging che stampa gli indirizzi delle connessioni sullo standard output. Un altro aspetto tralasciato è la gestione della terminazione dei processi figli, torneremo su questo più avanti quando tratteremo alcuni esempi di server più complessi.