IL server oscuro
come scrivere un TCP Server in C
Alfred: Pensa che prenderà il Batpod Signore? Bruce Wayne: In pieno giorno, Alfred? Non è molto discreto. Alfred: La Lamborghini, allora. Molto più discreta.
Questo articolo introduce un argomento un po’ oscuro, proprio come Il Cavaliere Oscuro, e cercheremo di farlo in maniera discreta, anche se, per il momento, non dispongo ancora di una Lamborghini…
![]() |
| …non è un Blog di Cinema questo, neh?… |
Come si intuisce dal titolo, oggi parleremo di TCP Server (o Socket Server, fa lo stesso). Questo breve post, insieme al prossimo in cui parleremo (ovviamente) di TCP Client, ci servirà per introdurre adeguatamente gli articoli finali (quelli in cui metteremo realmente molta carne al fuoco) che saranno incentrati su un argomento ancora più oscuro, e cioè OpenSSL. Ma non divaghiamo troppo e concentriamoci sull’argomento di oggi: spero che tutti sappiate cos’è un TCP Server, se no vi consiglio una utile lettura con tanto di esempio (ma il mio esempio è meglio!), così non perdo tempo e posso passare direttamente al codice. Eccolo!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MYBUFSIZE 1024
#define BACKLOG 10 // per listen()
int main(int argc, char *argv[])
{
// test argomenti
if (argc != 2) {
// errore args
printf("%s: numero argomenti errato\n", argv[0]);
printf("uso: %s port [i.e.: %s 8888]\n", argv[0], argv[0]);
return EXIT_FAILURE;
}
// crea un socket
int my_socket;
if ((my_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
// errore socket()
fprintf(stderr, "%s: non posso creare il socket (%s)\n", argv[0], strerror(errno));
return EXIT_FAILURE;
}
// prepara la struttura sockaddr_in per questo server
struct sockaddr_in server; // (locale) server socket info
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET; // set address family
server.sin_addr.s_addr = INADDR_ANY; // set server address per qualunque interfaccia
server.sin_port = htons(atoi(argv[1])); // set port number del server
// assegnazione di un indirizzo al socket creato
if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
// errore bind()
fprintf(stderr, "%s: errore bind (%s)", argv[0], strerror(errno));
close(my_socket);
return EXIT_FAILURE;
}
// start ascolto con una coda di max BACKLOG connessioni
if (listen(my_socket, BACKLOG) < 0) {
// errore listen()
fprintf(stderr, "%s: errore listen (%s)\n", argv[0], strerror(errno));
close(my_socket);
return EXIT_FAILURE;
}
// accetta connessioni da un client entrante
printf("%s: attesa connessioni entranti...\n", argv[0]);
socklen_t socksize = sizeof(struct sockaddr_in);
struct sockaddr_in client; // (remote) client socket info
int client_sock;
if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) {
// errore accept()
fprintf(stderr, "%s: errore accept (%s)\n", argv[0], strerror(errno));
close(my_socket);
return EXIT_FAILURE;
}
// chiude il socket non più in uso
close(my_socket);
// loop di ricezione messaggi dal client
char client_msg[MYBUFSIZE];
int recv_size;
while ((recv_size = recv(client_sock, client_msg, MYBUFSIZE, 0)) > 0 ) {
// send messaggio di ritorno al client
printf("%s: ricevuto messaggio dal sock %d: %s\n", argv[0], client_sock, client_msg);
char server_msg[MYBUFSIZE];
sprintf(server_msg, "mi hai scritto: %s", client_msg);
if (send(client_sock, server_msg, strlen(server_msg), 0) < 0) {
// errore send()
fprintf(stderr, "%s: errore send (%s)\n", argv[0], strerror(errno));
close(client_sock);
return EXIT_FAILURE;
}
// clear buffer
memset(client_msg, 0, MYBUFSIZE);
}
// loop terminato: test motivo
if (recv_size < 0) {
// errore recv()
fprintf(stderr, "%s: errore recv (%s)\n", argv[0], strerror(errno));
close(client_sock);
return EXIT_FAILURE;
}
else if (recv_size == 0) {
// Ok: il client si è disconnesso
printf("%s: client disconnesso\n", argv[0]);
}
// esco con Ok
close(client_sock);
return EXIT_SUCCESS;
}
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale.
La struttura è quella classica e basica di un TCP Server:
- socket() – crea un socket
- prepara la struttura sockaddr_in per questo Server
- bind() – assegnazione di un indirizzo al socket creato
- listen() – start ascolto con una coda di max BACKLOG connessioni
- accept() – accetta connessioni da un Client entrante
- recv() – loop di ricezione messaggi dal Client
ovviamente esistono varianti di questa struttura, ma questa è quella classica. In quest’esempio, che ho scritto e testato appositamente per il blog (beh, in realtà ho adattato/modificato ad uso blog un po’ di codice che ho scritto per lavoro, non è certo il primo TCP Server che scrivo!), nel loop di lettura c’è il re-invio al Client del messaggio ricevuto: una vera sorpresa! Nel prossimo post, come accennato sopra, vedremo un TCP Client, così chiuderemo il cerchio e potremo testare sul serio una conversazione Client/Server.
Notare, poi, che il main() inizia con un bel test sugli argomenti con eventuale esempio di uso in caso di errore: questo dà la opportuna aria professionale al codice e non dovrebbe mai mancare in una applicazione di questo tipo.
Dal punto di vista strettamente stilistico ho scritto questo codice non rispettando il mio stile preferito (ci sono multipli punti di uscita) ma per un programma quasi-sequenziale come questo non è uno stile disprezzabile, e poi… mi è venuto così! Del resto a suo tempo (vedi il link appena citato) avevo anche detto che ci vuole un po’ di elasticità, mai essere troppo rigidi.
Un ultimo accenno sulla #define BACKLOG: trovare il valore adatto è un argomento quasi filosofico, per cui vi rimando a una ottima descrizione che ho trovato in linea. Ci rivediamo per il TCP Client e, come sempre, non trattenete il respiro nell’attesa…
Ciao e al prossimo post!

