Scrivere Software è un piacere. Un programma non solo deve funzionare bene ed essere efficiente (questo si dà per scontato), ma deve essere anche bello ed elegante da leggere, comprensibile e facile da manutenere, sia per l'autore che per eventuali lettori futuri. Programmare bene in C è un'arte.
Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...
Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.
venerdì 23 dicembre 2016
giovedì 8 dicembre 2016
L'ultimo degli Apache III - Il ritorno
come scrivere un modulo Apache in C - pt.3
Allora, facciamo il punto: negli ultimi tre post abbiamo parlato di moduli lighttpd (qui, qui e ancora qui). Nel primo dei tre avevo ricordato che l'argomento non era nuovo: anticamente avevo scritto due post (qui e qui) in cui si parlava di moduli per Apache, che è un parente stretto di lighttpd (famiglia Web Servers). Ecco, per chiudere in bellezza vi propongo un terzo episodio de "L'ultimo degli Apache" (o dei Mohicani?), con una (spero) interessante ampliazione di quello che si era scritto.
Bene, ne "L'ultimo degli Apache II - La vendetta" (che, avete appena riletto, immagino...) avevamo scritto un bel modulo elementare per Apache. Faceva poco (scriveva solo una presentazione nel browser) però era un buon inizio per entrare nel mondo dei moduli per Web Servers. Ecco, adesso riprenderemo quel modulo e gli aggiungeremo una funzione che estrae i dati POST inclusi in una eventuale petizione HTTP di tipo POST che arriva al modulo. Perché ho scelto di aggiungere proprio questa funzionalità? Beh, ovviamente perchè è abbastanza normale che un modulo tratti vari tipi di petizioni (GET, POST, ecc.), e poi, in particolare, ricordo che la prima volta che aggiunsi questa funzionalità in un modulo Apache che avevo scritto, notai che non erano disponibili molte indicazioni su questo argomento (e questo nonostante la enorme mole di documentazione Apache disponibile rispetto a quella di lighttpd).
Bene, senza stare a ripetere tutto il codice e la maniera di generarlo (c'è già tutto in "L'ultimo degli Apache II - La vendetta") riscriveremo solo la funzione myapmodHandler() e aggiungeremo una nuova funzione getPost(). Vai col codice!
L'originale funzione myapmodHandler() è diventata una sorta di funzione helloworld (beh, in pratica lo era anche prima), che distingue i tipi di petizione e, nel caso POST, chiama la funzione readPost() e scrive come risposta "Hello, world!" più i dati. Nel caso di petizioni GET o di petizioni POST senza dati si limita a scrivere "Hello, world!", e per altri tipi di petizione esce con errore.
La funzione readPost() è semplice ma non immediata: a suo tempo la derivai dall'unico esempio interessante e ben fatto che trovai, e che stava nel (ottimo) libro "Writing Apache Modules with Perl and C", che vi raccomando. Direi che nell'ultima versione di Apache, la 2.4, sono stati introdotti (e descritti qui) altri metodi di estrazione dei dati POST (vi auguro una buona lettura!), comunque io me l'ero cavata così, e il modulo funzionava (e funziona tutt'ora) benissimo.
Ah, il test, dimenticavo! Testare con petizioni POST non è semplice come farlo con le GET, dove è sufficiente (come detto nel post precedente) scrivere la URI del modulo nella barra degli indirizzi di Firefox o Chrome e aspettare la risposta. Per testare il nostro nuovo modulo è meglio usare un bel plugin del browser (tipo Poster) per semplificare il lavoro.
Va bene, per il momento credo che possiamo fermare per un tempo l'argomento moduli Apache/lighttpd. Giuro che nel prossimo post parlerò d'altro, non vorrei che pensaste che il C si usa solo per i Web Servers...
Ciao e al prossimo post!
![]() |
...veramente sono un Mohicano, non un Apache... |
Bene, senza stare a ripetere tutto il codice e la maniera di generarlo (c'è già tutto in "L'ultimo degli Apache II - La vendetta") riscriveremo solo la funzione myapmodHandler() e aggiungeremo una nuova funzione getPost(). Vai col codice!
// handler del modulo static int myapmodHandler( request_rec *reqrec) // request data { // test handler if (! reqrec->handler || strcmp(reqrec->handler, "myapmod")) return DECLINED; // set del appropriato content type ap_set_content_type(reqrec, "text/html"); // test metodo http if (reqrec->method_number == M_GET) { // petizione GET: scrive solo "Hello, World!" ap_rprintf(reqrec, "GET request: Hello, world!"); } else if (reqrec->method_number == M_POST) { // petizione POST: legge POST data char *data; if (readPost(reqrec, &data) != -1) { // scrive "Hello, World!" e mostra POST data ap_rprintf(reqrec, "POST request: Hello, world! postdata: %s", data); } else { // POST data non disponibile: scrive solo "Hello, World!" ap_rprintf(reqrec, "POST request: Hello, world!"); } } else return HTTP_METHOD_NOT_ALLOWED; // esce con OK return OK; } // funzione per leggere POST data static int readPost( request_rec *reqrec, // request data char **data) // buffer di destinazione per POST data { // setup del client per permettera che Apache legga il request body if (ap_setup_client_block(reqrec, REQUEST_CHUNKED_ERROR) == OK) { // determina se il client ha spedito dati if (ap_should_client_block(reqrec)) { // legge i dati di POST char argsbuffer[HUGE_STRING_LEN]; int rsize, len_read, rpos=0; long length = reqrec->remaining; *data = (char*)apr_pcalloc(reqrec->pool, length +1); // loop di inserzione dati nel buffer di destinazione while ((len_read = ap_get_client_block(reqrec, argsbuffer, sizeof(argsbuffer))) > 0) { if ((rpos + len_read) > length) rsize = length - rpos; else rsize = len_read; // copia un blocco di dati memcpy((char *)*data + rpos, argsbuffer, rsize); rpos += rsize; } // POST data letto: return OK return 0; } } // esce con NOK return -1; }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.
L'originale funzione myapmodHandler() è diventata una sorta di funzione helloworld (beh, in pratica lo era anche prima), che distingue i tipi di petizione e, nel caso POST, chiama la funzione readPost() e scrive come risposta "Hello, world!" più i dati. Nel caso di petizioni GET o di petizioni POST senza dati si limita a scrivere "Hello, world!", e per altri tipi di petizione esce con errore.
La funzione readPost() è semplice ma non immediata: a suo tempo la derivai dall'unico esempio interessante e ben fatto che trovai, e che stava nel (ottimo) libro "Writing Apache Modules with Perl and C", che vi raccomando. Direi che nell'ultima versione di Apache, la 2.4, sono stati introdotti (e descritti qui) altri metodi di estrazione dei dati POST (vi auguro una buona lettura!), comunque io me l'ero cavata così, e il modulo funzionava (e funziona tutt'ora) benissimo.
Ah, il test, dimenticavo! Testare con petizioni POST non è semplice come farlo con le GET, dove è sufficiente (come detto nel post precedente) scrivere la URI del modulo nella barra degli indirizzi di Firefox o Chrome e aspettare la risposta. Per testare il nostro nuovo modulo è meglio usare un bel plugin del browser (tipo Poster) per semplificare il lavoro.
Va bene, per il momento credo che possiamo fermare per un tempo l'argomento moduli Apache/lighttpd. Giuro che nel prossimo post parlerò d'altro, non vorrei che pensaste che il C si usa solo per i Web Servers...
Ciao e al prossimo post!
sabato 5 novembre 2016
Il grande lighttpd
come scrivere un modulo lighttpd in C - pt.3
Drugo: E se poi quello se la prende?
Bunny: A lui non importa niente di niente, è un nichilista.
Drugo: Ah, dev’essere faticoso da morire.
Ebbene si, siamo faticosamente arrivati al capitolo finale, oggi si scrive il codice del nostro mini-modulo lighttpd. Uff, che fatica per il Drugo...
Ma prima vediamo un attimo come è strutturato un modulo. Eccovi un piccolo schema esemplificativo (estratto dal nostro mod_helloworld.c creato nel post precedente: rileggere subito il post precedente!):
Allora, che parti del nostro modulo dobbiamo modificare per fare in modo che, usandolo, ci scriva "Hello, world!" nel browser? Visto che il problema è abbastanza semplice anche il codice da scrivere non sarà molto voluminoso e complesso: ci basterà modificare solo la callback mod_helloworld_uri_handler nella seguente maniera:
Alcune parti sono rimaste identiche alla callback originale (ma ho aggiunto dei commenti), e sono tipiche della struttura del codice di lighttpd: per non complicare troppo l'argomento non mi dilungherò su queste parti. Comunque fidatevi perché lighttpd è scritto e funziona come si deve.
La parte veramente nuova che ho scritto è quella che inizia col commento "test handler (return se errore)", dove si testa se la URI passata al browser contiene il nome del modulo e, in quel caso, esegue quello che vogliamo: copia in un buffer la scritta "Hello, world!" e la invia come risposta alla petizione. Quindi se noi scriviamo nella barra degli indirizzi di Firefox, Chrome, Opera (o qualsiasi altro browser meno IE, per favore...)
Il codice appena mostrato l'ho scritto e provato con lighttpd 1.4.33. Se usate una versione più recente (dalla 1.4.36 in avanti) scoprirete che qualcosa è cambiato a livello di gestione dei buffer, quindi ad esempio le linee:
Ciao e al prossimo post!
Bunny: A lui non importa niente di niente, è un nichilista.
Drugo: Ah, dev’essere faticoso da morire.
Ebbene si, siamo faticosamente arrivati al capitolo finale, oggi si scrive il codice del nostro mini-modulo lighttpd. Uff, che fatica per il Drugo...
![]() |
...che fatica... |
// init the plugin data INIT_FUNC(mod_helloworld_init) { ... } // destroy the plugin data FREE_FUNC(mod_helloworld_free) { ... } // handle plugin config and check values SETDEFAULTS_FUNC(mod_helloworld_set_defaults) { ... } // handle uri URIHANDLER_FUNC(mod_helloworld_uri_handler) { ... } // this function is called at dlopen() time and inits the callbacks int mod_helloworld_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; p->name = buffer_init_string("helloworld"); p->init = mod_helloworld_init; p->handle_uri_clean = mod_helloworld_uri_handler; p->set_defaults = mod_helloworld_set_defaults; p->cleanup = mod_helloworld_free; p->data = NULL; return 0; }Come si nota si usano delle macro (gentilmente fornite dall'ambiente di sviluppo lighttpd) per creare delle callback con cui inizializzare una struttura plugin che poi lighttpd userà per gestire il nostro modulo ogni qualvolta arrivi una petizione HTTP che ne richieda l'uso. Per chi vuole approfondire l'argomento callback è già stato ampiamente descritto qui e qui (immagino abbiate già letto quei post... o no?).
Allora, che parti del nostro modulo dobbiamo modificare per fare in modo che, usandolo, ci scriva "Hello, world!" nel browser? Visto che il problema è abbastanza semplice anche il codice da scrivere non sarà molto voluminoso e complesso: ci basterà modificare solo la callback mod_helloworld_uri_handler nella seguente maniera:
URIHANDLER_FUNC(mod_helloworld_uri_handler) { plugin_data *p = p_d; UNUSED(srv); // test modo (return se errore) if (con->mode != DIRECT) return HANDLER_GO_ON; // test uri path (return se errore) if (con->uri.path->used == 0) return HANDLER_GO_ON; mod_helloworld_patch_connection(srv, con, p); // test handler (return se errore) if (con->uri.path->ptr && strstr(con->uri.path->ptr, "helloworld")) { // scrive buffer buffer *b = chunkqueue_get_append_buffer(con->write_queue); BUFFER_APPEND_STRING_CONST(b, "<big>Hello, world!</big>"); // send header response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); con->http_status = 200; con->file_finished = 1; // handling finito return HANDLER_FINISHED; } else return HANDLER_GO_ON; }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.
Alcune parti sono rimaste identiche alla callback originale (ma ho aggiunto dei commenti), e sono tipiche della struttura del codice di lighttpd: per non complicare troppo l'argomento non mi dilungherò su queste parti. Comunque fidatevi perché lighttpd è scritto e funziona come si deve.
La parte veramente nuova che ho scritto è quella che inizia col commento "test handler (return se errore)", dove si testa se la URI passata al browser contiene il nome del modulo e, in quel caso, esegue quello che vogliamo: copia in un buffer la scritta "Hello, world!" e la invia come risposta alla petizione. Quindi se noi scriviamo nella barra degli indirizzi di Firefox, Chrome, Opera (o qualsiasi altro browser meno IE, per favore...)
http://127.0.0.1/helloworldavremo come risposta nel browser:
Hello, world!Ho scritto 127.0.0.1 (e si poteva scrivere anche localhost) perché, ovviamente, il primo test lo faremo in locale. Se poi avete voglia di aprire la vostra macchina al mondo esterno e usarla come un vero Web Server (in una rete locale o nel WWW) potrete ripetere il test usando il vostro indirizzo IP pubblico e otterrete lo stesso risultato. Il modulo funziona!
Il codice appena mostrato l'ho scritto e provato con lighttpd 1.4.33. Se usate una versione più recente (dalla 1.4.36 in avanti) scoprirete che qualcosa è cambiato a livello di gestione dei buffer, quindi ad esempio le linee:
if (con->uri.path->used == 0) ... buffer *buf = chunkqueue_get_append_buffer(con->write_queue);devono diventare:
if (buffer_is_empty(con->uri.path)) ... buffer *buf = buffer_init();ma questi sono dettagli. In futuro, come promesso, vi proporrò qualche modulo più sofisticato, con modifiche anche alle altre callback del modulo, ma questo per il momento può bastare, non gettiamo troppa carne al fuoco, se no ci stanchiamo come il Drugo...
Ciao e al prossimo post!
domenica 9 ottobre 2016
Il grande lighttpd
come scrivere un modulo lighttpd in C - pt.2
Drugo: Sai, questo... questo è un caso molto, molto complicato, Maude. Un sacco di input e di output. Sai, fortunatamente io rispetto un regime di droghe piuttosto rigido per mantenere la mente, diciamo, flessibile.Allora: la buona notizia è che per scrivere un modulo lighttpd non è necessario seguire lo stesso regime del Drugo... quella cattiva è che ci vuole un certo impegno, ma sarà molto più semplice dopo aver letto questo post (e il precedente, ovviamente... non l'avete ancora letto? Ma questa è la seconda parte!).
![]() |
faccia da "Ora so come si scrive un modulo lighttpd!" |
Per prima cosa scegliamo un nome e un azione... sarà un classico: lo chiameremo mod_helloworld e il modulo scriverà nel browser la frase "Hello, world!" (molto, ma molto, originale!).
Apriamo un terminale Linux, entriamo nella root-directory dell'ambiente di sviluppo ed eseguiamo i seguenti comandi (che ho numerato per descriverli uno a uno):
1. sudo gedit /etc/lighttpd/lighttpd.conf 2. gedit src/Makefile.am 3. cp src/mod_skeleton.c src/mod_helloworld.c 4. gedit src/mod_helloworld.c 5. ./autogen.sh 6. ./configure 7. make 8. sudo make install 9. sudo cp /usr/local/lib/mod_helloworld.so /usr/lib/lighttpd 10. sudo /etc/init.d/lighttpd restartAllora: con 1. editiamo (con gedit, pluma, vim, geany... o quello che preferite) il file di configurazione di lighttpd per aggiungere alla lista dei moduli installati il nostro nuovo modulo: cercate la lista "server.modules" e inserite, dopo l'ultimo modulo listato, il nuovo. Fatto ciò l'aspetto della lista sarà del tipo:
server.modules = ( "mod_access", "mod_alias", "mod_compress", "mod_redirect", "mod_helloworld", )Con 2. editiamo il file Makefile.am del package. Rispetto al package originale stiamo aggiungendo un modulo, quindi bisogna cercare l'ultimo modulo standard presente (normalmente è mod_accesslog). Troveremo quattro linee così:
lib_LTLIBRARIES += mod_accesslog.la mod_accesslog_la_SOURCES = mod_accesslog.c mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined mod_accesslog_la_LIBADD = $(common_libadd)Copiamo queste linee e le replichiamo immediatamente sotto, sostituendo nelle NUOVE quattro linee mod_accesslog con mod_helloworld. Salviamo e usciamo: ora il nostro Makefile.am è pronto per gestire anche il nuovo modulo. Le linee aggiunte saranno queste:
lib_LTLIBRARIES += mod_helloworld.la mod_helloworld_la_SOURCES = mod_helloworld.c mod_helloworld_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined mod_helloworld_la_LIBADD = $(common_libadd)Con 3. copiamo il file mod_skeleton.c in un nuovo file che chiameremo mod_helloworld.c: mod_skeleton.c è un template file contenente lo scheletro base di ogni modulo: questo file ce lo mettono gentilmente a disposizione gli ottimi sviluppatori di lighttpd per facilitarci la scrittura di nuovi moduli.
Con 4. editiamo mod_helloworld.c e, con un comando di sostituzione globale, cambiamo tutte le ricorrenze della parola skeleton in helloworld. Per il momento salviamo così il file e usciamo dall'editor.
Con la sequenza 5.6.7.8. (vista già nel post precedente, ricordate?) compiliamo e installiamo il nuovo modulo. È possibile che nella fase 6. (quella di configurazione) il processo vi dica che non trova alcune librerie: normalmente l'errore si risolve usando:
sudo apt-get install libpcre3-dev libbz2-deve ripetendo, poi, il punto 6.
Con 9. copiamo il nuovo modulo generato (che ha la forma di una libreria dinamica, una .so) nella directory di sistema che contiene i moduli lighttpd.
Con 10., finalmente, riavviamo lighttpd e, se non ci appare nessun errore il nostro Web Server è già pronto per usare il nuovo modulo (che però, per il momento, è semivuoto: come detto sopra è solo lo scheletro di un modulo).
Cosa ci manca a questo punto? Beh, ovviamente, dobbiamo aggiungere un po' di codice nel nostro modulo per fargli fare qualcosa (nel nostro caso mostrarci un bel "Hello, world!"). Ma questo lo vedremo nella prossima puntata, e, come sempre, non trattenete il respiro nell'attesa...
Ciao e al prossimo post!
sabato 17 settembre 2016
Il grande lighttpd
come scrivere un modulo lighttpd in C - pt.1
Poliziotto: E nella valigetta?Ecco, a meno che non vi interessi fare lo stesso lavoro del mitico Drugo (The Dude), è una buona idea disporre di un know-how di valore. Questo è un post ad alto valore aggiunto, e lo è semplicemente considerando la legge della domanda e dell'offerta: tratteremo un argomento di cui non è disponibile una grande base di conoscenza, quindi è un post di valore.
Drugo: Oh, beh, documenti, solo documenti. Già, solo i miei documenti. Documenti di lavoro.
Poliziotto: Che lavoro fa?
Drugo: Sono disoccupato.
![]() |
...vorrei scrivere un modulo, ma sono un po' stanco... |
E veniamo a lighttpd: è un Web Server diffuso (ma molto meno di Apache) e con caratteristiche tecniche di prima classe: ha prestazioni paragonabili ad Apache ma è enormemente più leggero: poco carico di CPU, poca memoria, ecc. Praticamente è un must per sistemi embedded o, più in generale, per sistemi dove sono necessarie alte prestazioni usando poche risorse. Sfortunatamente lighttpd non ha dietro una grande organizzazione di sviluppo, e, proporzionalmente alla minore diffusione, non ha neanche un grande esercito di professionisti e appassionati che ci lavorano. Grazie a ciò NON si trova in rete una grande quantità di documentazione e guide, sia ufficiali che non. Scrivere un modulo lighttpd non è facilissimo, e, con il poco supporto disponibile, è una missione complicata.
Nello specifico: lighttpd è un gran prodotto scritto e mantenuto da una ristretta comunità di eccellenti programmatori, ma, non avendo dietro una grande organizzazione, si perde un po' nei dettagli: con più documentazione e guide ufficiali si aumenterebbe la diffusione, il che aumenterebbe anche la comunità di utilizzatori/sviluppatori (e quindi la documentazione non ufficiale), e, alla fin fine, aumenterebbe il successo del prodotto. Ma questo non succede e quindi siamo davanti a un caso classico di sindrome del cane che si morde la coda.
lighttpd è in continua evoluzione, con prestazioni e affidabilità in crescendo, ma, per la carenza nei dettagli citata prima, può anche succedere che, tra una release e l'altra, ci si dimentichi della retro-compatibilità e si introducano delle modifiche alla API di programmazione che invalidano molti dei moduli (faticosamente) scritti dagli sviluppatori esterni (è successo anche a me con un modulo che ho scritto per la rel.1.4.33 e che con la rel.1.4.36 ha smesso di funzionare!).
Va bene, bando alle ciance: scriveremo un modulo elementare per lighttpd, che sarà una buona base per la scrittura di moduli più complessi (su cui vi fornirò alcune dritte in post futuri). La prima attività è, ovviamente, installare lighttpd sul PC (e, se anteriormente installato, rimuovere prima Apache). Cercate, con il nostro amico Google, una delle molte guide (ad esempio questa) per trasformare un PC in un server LLMP (o WLMP) (non vi descrivo io la procedura per non dilungarmi troppo, ma, vi garantisco, è abbastanza semplice). Ovviamente a fine procedura verificate se il Web Server funziona correttamente (di solito le guide vi dicono come farlo).
Poi bisogna installare il necessario per sviluppare il nostro modulo. Le istruzioni che seguono sono (ovviamente) per Linux. Per altri sistemi della famiglia UNIX (BSD, OS X, ecc.) il procedimento si adatta intuitivamente, mentre, per quell'altro sistema che non voglio neanche nominare (comincia per W...), mi dispiace ma vi dovrete arrangiare da soli (se lo conosci lo eviti, ed io, vi assicuro, lo conosco bene).
Allora: scaricatevi dal sito ufficiale di lighttpd il tar di sviluppo corrispondente alla versione che avete appena installato sul sistema (sono disponibili anche i tar di versioni precedenti all'ultima). Questo tar serve per creare sul sistema il giusto ambiente di sviluppo e installazione di nuovi moduli. Decomprimete (dove volete) il tar, entrate nella directory creata e provate a compilare e installare la release (lo so, la stessa versione l'avete appena installata sul sistema, ma questo passo serve a verificare se il sistema di sviluppo funziona correttamente). Supponendo di usare la versione 1.4.33 scriveremo:
tar zxvf lighttpd-1.4.33.tar.gz cd lighttpd-1.4.33 ./autogen.sh ./configure make sudo make install sudo /etc/init.d/lighttpd restartVerificate se il Web Server funziona ancora (nella stessa maniera usata per la prima installazione) e, se tutto è OK, siamo pronti per scrivere/compilare/installare il nostro nuovo modulo. Ma questo lo vedremo nella prossima puntata...
Ciao e al prossimo post!
martedì 23 agosto 2016
UDPhunter
come scrivere un UDP Client in C - pt.2
Siamo alla fine di Agosto e il ritorno dalle vacanze è sempre duro. Quindi, per risparmiare tempo e fatica cercherò di riciclare parte del testo di un mio vecchio post ben collegato a questo. Del resto questa è la seconda parte (il Client) dei post su UDP e, quindi, la seconda parte dei miei vecchi post su TCP cade a fagiolo. Il codice è, ovviamente, tutto nuovo.
Come si intuisce dal titolo questa volta è il turno del UDP Client. Spero che tutti sappiate cos'è, 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!
La struttura è quella classica e basica di un UDP Client:
Per quanto riguarda il flusso e lo stile del main() valgono le note elencate nel post sul Server. Per testarlo è sufficiente aprire due terminali (UNIX o Linux, ovviamente), e avviare in uno il Server e nell'altro il Client; se proviamo in una macchina sola il Client deve, come è logico, collegarsi al Server su localhost. Per l'argomento port si può usare un numero qualsiasi scelto tra quelli non riservati (e bisogna usare lo stesso port per Client e Server!) Il risultato sarà il seguente:
terminale 1 (Server):
Ciao e al prossimo post!
![]() |
oggi parleremo degli UDP client... |
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <errno.h> #define MYBUFSIZE 1024 int main(int argc, char *argv[]) { // test argomenti if (argc != 3) { // errore args printf("%s: numero argomenti errato\n", argv[0]); printf("uso: %s host port [i.e.: %s 127.0.0.1 8888]\n", argv[0], argv[0]); return EXIT_FAILURE; } // crea un socket int my_socket; if ((my_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { // errore socket() printf("%s: non posso creare il socket (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // prepara la struttura sockaddr_in per il server remoto struct sockaddr_in server; // (remote) server socket info memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // set address family server.sin_addr.s_addr = inet_addr(argv[1]); // set server address server.sin_port = htons(atoi(argv[2])); // set server port number // loop di comunicazione col server remoto for (;;) { // compone messaggio per il server remoto char my_msg[MYBUFSIZE]; printf("Scrivi un messaggio per il Server remoto: "); scanf("%s", my_msg); // send messaggio al server remoto if (sendto(my_socket, my_msg, strlen(my_msg), 0, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore send() printf("%s: errore send (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // riceve una risposta dal server remoto memset(my_msg, 0, MYBUFSIZE); if (recvfrom(my_socket, my_msg, MYBUFSIZE, 0, NULL, NULL) < 0) { // errore recv() printf("%s: errore recv (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // mostra la risposta printf("%s: risposta Server: %s\n", argv[0], my_msg); } // esco con Ok 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 UDP Client:
- socket() - crea un socket
- prepara la struttura sockaddr_in per il server remoto
- sendto() + recvfrom() - loop di comunicazione col server remoto
Per quanto riguarda il flusso e lo stile del main() valgono le note elencate nel post sul Server. Per testarlo è sufficiente aprire due terminali (UNIX o Linux, ovviamente), e avviare in uno il Server e nell'altro il Client; se proviamo in una macchina sola il Client deve, come è logico, collegarsi al Server su localhost. Per l'argomento port si può usare un numero qualsiasi scelto tra quelli non riservati (e bisogna usare lo stesso port per Client e Server!) Il risultato sarà il seguente:
terminale 1 (Server):
aldo@ao-linux-nb:~/blogtest$ ./udpserver 8888 ./udpserver: ricevuto messaggio dal sock 3: pippo ./udpserver: ricevuto messaggio dal sock 3: plutoterminale 2 (Client):
aldo@ao-linux-nb:~/blogtest$ ./udpclient 127.0.0.1 8888 Scrivi un messaggio per il Server remoto: pippo ./udpclient: risposta Server: mi hai scritto: pippo Scrivi un messaggio per il Server remoto: pluto ./udpclient: risposta Server: mi hai scritto: pluto Scrivi un messaggio per il Server remoto: ^CCome si nota il Client e il Server si parlano e quando il Client si scollega (con un brutale Ctrl-C) il Server non se ne accorge (è una comunicazione connectionless). Missione compiuta!
Ciao e al prossimo post!
sabato 9 luglio 2016
UDPhunter
come scrivere un UDP Server in C - pt.1
Spesso si può fare la stessa cosa in due (o magari 1000...) maniere differenti. Non necessariamente buone tutte. Ad esempio da un buon romanzo come Red Dragon sono stati tratti due film: uno è un capolavoro di Michael Mann, mentre l'altro è una vera schifezza che sarebbe stato meglio non girare.
Quando pensiamo alle comunicazioni basate sui Socket pensiamo sempre ai TCP Client/Server, e ci dimentichiamo dell'esistenza di UDP. Ecco, con UDP si possono fare cose eccellenti, sempre considerando quello che dice il manuale:
Per cui vi propongo un UDP Server che è quasi una copia del TCP server visto qui, e che già a prima vista si nota più semplice. Vai col codice!
La struttura è quella classica e basica di un UDP Server:
Come (spero) avrete notato, invece delle funzioni send/recv si usano sendto/recvfrom (che sono identiche alle controparti TCP ma aggiungono i dati dei sender/receiver) e, inoltre, mancano le fasi di listen e accept, il che rende il Server più semplice da scrivere e anche da usare: si possono fare sequenze di start/stop/restart di Server e Client nell'ordine che si vuole e la comunicazione, magicamente, si restaurerà sempre, mentre la versione TCP (immagino l'abbiate testata, e se no: correte a farlo!) che ha una fase di connessione, necessita di una più rigorosa sequenza di start/stop per funzionare.
Ci rivediamo per il UDP Client e, come sempre, non trattenete il respiro nell'attesa...
Ciao e al prossimo post!
![]() |
Mr UDP, Mr TCP e la fotografia abbagliante di un capolavoro |
...It implements a connectionless, unreliable datagram packet service. Packets may be reordered or duplicated before they arrive. UDP generates and checks checksums to catch transmission errors...Non facciamoci tradire dalla (preoccupante) parola unreliable: se lo usiamo nelle applicazioni giuste (quelle dove la perdita/duplicazione di un pacchetto non è un errore tragico... il VoIP, per esempio) le doti di UDP (velocità, leggerezza, facilità di implementazione) vengono fuori alla grande.
Per cui vi propongo un UDP Server che è quasi una copia del TCP server visto qui, e che già a prima vista si nota più semplice. Vai col codice!
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <errno.h> #define MYBUFSIZE 1024 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_DGRAM, IPPROTO_UDP)) < 0) { // errore socket() printf("%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; // (local) 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 for any interface server.sin_port = htons(atoi(argv[1])); // set server port number // bind informazioni del server al socket if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore bind() printf("%s: errore bind (%s)", argv[0], strerror(errno)); return EXIT_FAILURE; } // loop di ricezione messaggi dal client struct sockaddr_in client; // appoggio per dati del client per poter rispondere socklen_t socksize = sizeof(struct sockaddr_in); char client_msg[MYBUFSIZE]; while (recvfrom(my_socket, client_msg, MYBUFSIZE, 0, (struct sockaddr *)&client, &socksize) > 0) { // send messaggio di ritorno al client printf("%s: ricevuto messaggio dal sock %d: %s\n", argv[0], my_socket, client_msg); char server_msg[MYBUFSIZE]; sprintf(server_msg, "mi hai scritto: %s", client_msg); if (sendto(my_socket, server_msg, strlen(server_msg), 0, (struct sockaddr *)&client, sizeof(client)) < 0) { // errore send() printf("%s: errore send (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // clear buffer memset(client_msg, 0, MYBUFSIZE); } // errore recv(): il "client disconnected" con recv_size==0 non esiste perché questo server è connectionless printf("%s: errore recv (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; }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 UDP Server:
- socket() - crea un socket
- prepara la struttura sockaddr_in per questo server
- bind() - bind informazioni del server al socket
- recvfrom() - loop di ricezione messaggi dal client
Come (spero) avrete notato, invece delle funzioni send/recv si usano sendto/recvfrom (che sono identiche alle controparti TCP ma aggiungono i dati dei sender/receiver) e, inoltre, mancano le fasi di listen e accept, il che rende il Server più semplice da scrivere e anche da usare: si possono fare sequenze di start/stop/restart di Server e Client nell'ordine che si vuole e la comunicazione, magicamente, si restaurerà sempre, mentre la versione TCP (immagino l'abbiate testata, e se no: correte a farlo!) che ha una fase di connessione, necessita di una più rigorosa sequenza di start/stop per funzionare.
Ci rivediamo per il UDP Client e, come sempre, non trattenete il respiro nell'attesa...
Ciao e al prossimo post!
venerdì 3 giugno 2016
Sprintf Driver
perché non bisogna usare la sprintf in C
"Ma dici a me? Ma dici a me?... Ma dici a me?...". Si, proprio come il grande De Niro in Taxi Driver, questa è stata la mia reazione (incredula) quando ho scoperto (molti anni fa, oramai) che, dopo anni e anni di onorato uso, avrei dovuto smettere di usare la sprintf().
Beh, in effetti se la usi bene e hai il 100% di controllo sul codice scritto puoi anche usarla senza grossi problemi ma, come dicono gli inglesi, la sprintf() è error prone, induce facilmente a errori, anche gravi. Il problema più grave ed evidente con la sprintf() si chiama buffer overflow, e non credo che sia necessario spenderci molte parole: se il buffer che passiamo come primo argomento non è correttamente dimensionato il disastro è dietro l'angolo.
Fortunatamente ci viene in aiuto la snprintf(), che è della stessa famiglia ma molto più sicura. Vediamo i due prototipi a confronto:
E adesso vi propongo un piccolo esempio reale: prendiamo una nostra vecchia conoscenza scritta per un vecchio post, la getDateUsec() e la scriviamo in due versioni, una buona e una cattiva (bad). Vediamo:
E già che siamo in argomento sprintf() un piccolo consiglio un po' OT: se dovete aggiungere sequenzialmente delle stringhe (in un loop, ad esempio) su una stringa base (per comporre un testo, ad esempio) non fate mai cosi:
Ciao e al prossimo post!
![]() |
...e tu dici a me di non usare più la sprintf? A me?... |
Fortunatamente ci viene in aiuto la snprintf(), che è della stessa famiglia ma molto più sicura. Vediamo i due prototipi a confronto:
int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...);La snprintf() ci obbliga a mettere il size del buffer come secondo argomento, per cui è facilissimo prendere l'abitudine a scrivere in un modo error-free come questo:
char buffer[32]; snprintf(buffer, sizeof(buffer), "Hello world!");Se al posto di "Hello world!" avessimo scritto una stringa di più di 32 chars, nessun problema: la snprintf() la tronca opportunamente e siamo salvi.
E adesso vi propongo un piccolo esempio reale: prendiamo una nostra vecchia conoscenza scritta per un vecchio post, la getDateUsec() e la scriviamo in due versioni, una buona e una cattiva (bad). Vediamo:
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <time.h> // prototipi locali char *getDateUsec(char *dest, size_t size); char *badGetDateUsec(char *dest); // funzione main int main(int argc, char* argv[]) { // chiama getDateUsec (o badGetDateUsec) e scrive il risultato char dest[12]; printf("date con usec: %s\n", getDateUsec(dest, sizeof(dest))); //printf("date con usec: %s\n", badGetDateUsec(dest)); return EXIT_SUCCESS; } // getDateUsec() - Genera una stringa con data e ora (usa i microsecondi) char *getDateUsec(char *dest, size_t size) { // get time (con gettimeofday()+localtime() invece di time()+localtime() per ottenere i usec) struct timeval tv; gettimeofday(&tv, NULL); struct tm *tmp = localtime(&tv.tv_sec); // format stringa destinazione dest(deve essere allocata dal chiamante) e aggiunge i usec char fmt[128]; strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp); snprintf(dest, size, fmt, tv.tv_usec); // return stringa destinazione dest return dest; } // badGetDateUsec() - Genera una stringa con data e ora (usa i microsecondi) (versione bad) char *badGetDateUsec(char *dest) { // get time (con gettimeofday()+localtime() invece di time()+localtime() per ottenere i usec) struct timeval tv; gettimeofday(&tv, NULL); struct tm *tmp = localtime(&tv.tv_sec); // format stringa destinazione dest(deve essere allocata dal chiamante) e aggiunge i usec char fmt[128]; strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp); sprintf(dest, fmt, tv.tv_usec); // return stringa destinazione dest return dest; }Ecco, a suo tempo, per semplificare, avevo scritto una getDateUsec() che era, in realtà, una badGetDateUsec() (e in seguito, per precisione, ho provveduto a modificarla anche sul post). Quella versione funzionava ma poteva creare problemi, mentre la nuova versione è molto più sicura. Provate a compilare l'esempio, dove, volutamente, ho sottodimensionato il buffer di destinazione: commentando la badGetDateUsec() e usando la getDateUsec(), funziona perfettamente, troncando l'output a 12 chars. Se, invece, commentate la getDateUsec() e usate la badGetDateUsec() il programma si schianta durante l'esecuzione. Provare per credere!
E già che siamo in argomento sprintf() un piccolo consiglio un po' OT: se dovete aggiungere sequenzialmente delle stringhe (in un loop, ad esempio) su una stringa base (per comporre un testo, ad esempio) non fate mai cosi:
char buf[256] = ""; for (int i = 0; i < 5; i++) sprintf(buf, "%s aggiunto alla stringa %d\n", buf, i);il metodo qui sopra sembra funzionare, ma, in realtà, funziona quando c'ha voglia lui. Fate invece così:
char buf[256] = ""; for (int i = 0; i < 5; i++) { char tmpbuf[256]; sprintf(tmpbuf, "%s aggiunto alla stringa %d\n", buf, i); sprintf(buf, "%s", tmpbuf); }E se non ci credete provate a passare il codice con un lint tipo cppchek (che è sempre una buona idea) o consultate il manuale della sprintf():
C99 and POSIX.1-2001 specify that the results are undefined if a call to sprintf(), snprintf(), vsprintf(), or vsnprintf() would cause copy‐ ing to take place between objects that overlap (e.g., if the target string array and one of the supplied input arguments refer to the same buffer).E, ovviamente, anche in quest'ultimo esempio (fatto per semplicità con la sprintf()) sarebbe raccomandabile usare la snprintf().
Ciao e al prossimo post!
Iscriviti a:
Post (Atom)