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.
giovedì 24 dicembre 2015
domenica 6 dicembre 2015
Mission: Impossible - XML Deserializer
come scrivere un XML Deserializer in C
Come promesso torniamo con una nuova missione (non tanto) impossibile per proporre un Deserializer complementare al Serializer dell'ultimo post. Ovviamente, se non avete ancora letto il post precedente, dovreste:
1) vergognarvi
2) andare a leggerlo. Subito!
Ecco, se state leggendo queste righe può darsi che avete già passato i due punti precedenti, quindi possiamo continuare. Useremo anche questa volta la libXML2 per scrivere una funzione che riesce a estrarre tutte le coppie key-value che compongono un documento XML.
Vai col codice!
1) codice auto-esplicativo, ampiamente commentato e... i commenti parlano da soli.
2) il main() serve solo a lanciare la funzione deserialize(): concentratevi su quella.
La funzione, in realtà è divisa in due parti: deserialize() prepara la lettura del documento e recurDoc() (che è una funzione ricorsiva che richiama se stessa) ricorre il documento n-volte fino a quando ci sono campi da leggere.
Questo esempio è adatto a leggere qualsiasi file XML che gli si passa come argomento, e si può specializzare facilmente: ad esempio se gli passate un file di struttura nota (per esempio si può provare con il catalogo XML di film prodotto dal nostro precedente Serializer) potete riempire con i campi key-value letti i campi della struttura dati corrispondente (nel nostro caso la struct Catalog). Insomma, con un po' di immaginazione la coppia Serializer/Deserializer che vi ho proposto, permette un sacco di interessanti attività in programmi che usano dati in formato XML. Buon lavoro...
Vi ricordo nuovamente che, per testare l'esempio su Linux (cosa che ho fatto, ovviamente...) bisogna installare prima la libXML2 (dal repository della distribuzione), e poi compilare il programma con:
1) vergognarvi
2) andare a leggerlo. Subito!
Ecco, se state leggendo queste righe può darsi che avete già passato i due punti precedenti, quindi possiamo continuare. Useremo anche questa volta la libXML2 per scrivere una funzione che riesce a estrarre tutte le coppie key-value che compongono un documento XML.
![]() |
Sempre più facile... |
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <libxml/parser.h> // prototipi locali void deserialize(const char* document); void recurDoc(xmlDocPtr doc, xmlNodePtr cur); // main del programma di test int main(int argc, char **argv) { // test argomenti if (argc <= 1) { printf("Usage: %s docname\n", argv[0]); return(0); } /* libxml2: initializza la libreria e testa potenziali ABI mismatches tra la versione compilata e la attuale shared library usata */ LIBXML_TEST_VERSION; // deserializza XML deserialize(argv[1]); // esce con Ok return EXIT_SUCCESS; } // funzione deserialize() void deserialize( const char* document) { // salta i blank nodes xmlKeepBlanksDefault(0); // estrae il documento dal file xmlDocPtr doc; if ((doc = xmlParseFile(document)) == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return; } // test se il documento non è vuoto xmlNodePtr cur; if ((cur = xmlDocGetRootElement(doc)) == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return; } // ricorre e libera il documento recurDoc(doc, cur); xmlFreeDoc(doc); } // funzione ricorsiva di lettura del documento void recurDoc( xmlDocPtr doc, xmlNodePtr cur) { // loop di lettura xmlChar *value; cur = cur->xmlChildrenNode; while (cur != NULL) { // estrae e libera un elemento if ((value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) { printf("key: %-10s - value: %s\n", cur->name, value); xmlFree(value); } // chiamata ricorsiva recurDoc(doc, cur); // passa al prossimo elemento cur = cur->next; } // esce return; }E, come sempre:
1) codice auto-esplicativo, ampiamente commentato e... i commenti parlano da soli.
2) il main() serve solo a lanciare la funzione deserialize(): concentratevi su quella.
La funzione, in realtà è divisa in due parti: deserialize() prepara la lettura del documento e recurDoc() (che è una funzione ricorsiva che richiama se stessa) ricorre il documento n-volte fino a quando ci sono campi da leggere.
Questo esempio è adatto a leggere qualsiasi file XML che gli si passa come argomento, e si può specializzare facilmente: ad esempio se gli passate un file di struttura nota (per esempio si può provare con il catalogo XML di film prodotto dal nostro precedente Serializer) potete riempire con i campi key-value letti i campi della struttura dati corrispondente (nel nostro caso la struct Catalog). Insomma, con un po' di immaginazione la coppia Serializer/Deserializer che vi ho proposto, permette un sacco di interessanti attività in programmi che usano dati in formato XML. Buon lavoro...
Vi ricordo nuovamente che, per testare l'esempio su Linux (cosa che ho fatto, ovviamente...) bisogna installare prima la libXML2 (dal repository della distribuzione), e poi compilare il programma con:
gcc desxml.c -I/usr/include/libxml2 -o desxml -lxml2Ciao e al prossimo post!
sabato 28 novembre 2015
Mission: Impossible - XML Serializer
come scrivere un XML Serializer in C
La missione di oggi, in realtà, non è affatto impossibile: scrivere un XML Serializer in C, specialmente aiutandosi con una ottima libreria open-source come la libXML2 (licenza MIT), è relativamente semplice. Cosa è un serializzatore? È, semplicemente, una funzione che ci permette di trasformare una struttura dati, interna a un programma, nel suo equivalente in linguaggio XML.
Andiamo al sodo: supponiamo di avere la seguente struttura dati (anzi, una struttura annidata, complichiamoci un po' la vita):
Il main() è solo funzionale all'esempio: inizializza la libreria (leggete il commento!), prepara i dati per il test, li passa al Serializer, scrive i risultati in un file.
Quello che conta è la funzione serialize(), che si può usare dove e come si vuole in un programma che usa le strutture dati descritte sopra, e che, soprattutto, può essere usata come esempio per scrivere funzioni analoghe per altre strutture dati: è molto semplice e si può adattare facilmente a qualsiasi altro caso. Come si nota usa varie funzioni della libreria libXML2, e il meccanismo che permette di riempire il documento destino è semplice: si crea un nodo di partenza (root-node) e si aggiungono figli (child-nodes) seguendo la forma della struttura dati originale.
Dimenticavo: per testare l'esempio su Linux (cosa che ho fatto, ovviamente...) bisogna installare prima la libXML2 (dal repository della distribuzione), e poi compilare il programma con:
Ciao e al prossimo post!
![]() |
fortuna che non era impossibile... |
// tipo Film per test typedef struct { char title[32]; char director[32]; int year; } Film; // tipo Catalog per test typedef struct { time_t t_lastupd; Film films[2]; } Catalog;in un programma la possiamo riempire con un po' di dati (beh, in questo caso pochi, è solo un esempio) e con il nostro XML Serializer cerchiamo di ottenere un documento XML tipo questo:
<?xml version="1.0"?> <CATALOG> <LASTUPDATE>28/11/15 12:26:04</LASTUPDATE> <FILM> <TITLE>Mad Max 2: The Road Warrior</TITLE> <DIRECTOR>George Miller</DIRECTOR> <YEAR>1979</YEAR> </FILM> <FILM> <TITLE>Mad Max: Fury Road</TITLE> <DIRECTOR>George Miller</DIRECTOR> <YEAR>2015</YEAR> </FILM> </CATALOG>Ok, visto che siete impazienti di sapere come si fa, passiamo subito al codice:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <libxml/parser.h> // tipo Film per test typedef struct { char title[32]; char director[32]; int year; } Film; // tipo Catalog per test typedef struct { time_t t_lastupd; Film films[2]; } Catalog; // prototipi locali char* serialize(char* document, const Catalog* catalog); // main() del programma di test int main(int argc, char* argv[]) { // test argomenti if (argc != 1) { printf("%s: wrong arguments counts\n", argv[0]); printf("usage: %s [e.g.: %s]\n", argv[0], argv[0]); return EXIT_FAILURE; } /* libxml2: initializza la libreria e testa potenziali ABI mismatches tra la versione compilata e la attuale shared library usata */ LIBXML_TEST_VERSION; // prepara dati per test char dest_document_xml[2048]; Catalog catalog; catalog.t_lastupd = time(NULL); strcpy(catalog.films[0].title, "Mad Max 2: The Road Warrior"); strcpy(catalog.films[0].director, "George Miller"); catalog.films[0].year = 1979; strcpy(catalog.films[1].title, "Mad Max: Fury Road"); strcpy(catalog.films[1].director, "George Miller"); catalog.films[1].year = 2015; // serializza XML serialize(dest_document_xml, &catalog); // scrive i risultati in un file FILE* fp = fopen("catalog.xml", "w"); fprintf(fp, "%s", dest_document_xml); fclose(fp); // esce con Ok return EXIT_SUCCESS; } // funzione serialize() char* serialize( char* document, // documento destino serializzato const Catalog* catalog) // struttura documento { // crea documento con root node <CATALOG> xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); xmlNodePtr root_node = xmlNewNode(NULL, BAD_CAST "CATALOG"); xmlDocSetRootElement(doc, root_node); // aggiunge <LASTUPD> child-field al nodo <CATALOG> char str_lastupd[64]; strftime(str_lastupd, sizeof(str_lastupd), "%d/%m/%y %H:%M:%S", localtime(&catalog->t_lastupd)); xmlNewChild(root_node, NULL, BAD_CAST "LASTUPDATE", BAD_CAST str_lastupd); // loop per aggiungere i child-nodes <FILM> e child-fields al nodo <CATALOG> int i; for (i = 0; i < sizeof(catalog->films) / sizeof(Film); i++) { // aggiunge il child-node <FILM> al nodo <CATALOG> xmlNodePtr film_node = xmlNewChild(root_node, NULL, BAD_CAST "FILM", BAD_CAST NULL); // aggiunge i child-fields al nodo <FILM> xmlNewChild(film_node, NULL, BAD_CAST "TITLE", BAD_CAST catalog->films[i].title); xmlNewChild(film_node, NULL, BAD_CAST "DIRECTOR", BAD_CAST catalog->films[i].director); char s_year[8]; sprintf(s_year, "%d", catalog->films[i].year); xmlNewChild(film_node, NULL, BAD_CAST "YEAR", BAD_CAST s_year); } // copia il document al documento destino xmlChar *xmlbuff; int buffersize; xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1); strcpy(document, (char *)xmlbuff); // libera risorse xmlFree(xmlbuff); xmlFreeDoc(doc); xmlCleanupParser(); // return un pointer al document formattato return document; }Ok, come vedete il codice è (come sempre in questo Blog) 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 (al solito), qualche dettaglio strutturale.
Il main() è solo funzionale all'esempio: inizializza la libreria (leggete il commento!), prepara i dati per il test, li passa al Serializer, scrive i risultati in un file.
Quello che conta è la funzione serialize(), che si può usare dove e come si vuole in un programma che usa le strutture dati descritte sopra, e che, soprattutto, può essere usata come esempio per scrivere funzioni analoghe per altre strutture dati: è molto semplice e si può adattare facilmente a qualsiasi altro caso. Come si nota usa varie funzioni della libreria libXML2, e il meccanismo che permette di riempire il documento destino è semplice: si crea un nodo di partenza (root-node) e si aggiungono figli (child-nodes) seguendo la forma della struttura dati originale.
Dimenticavo: per testare l'esempio su Linux (cosa che ho fatto, ovviamente...) bisogna installare prima la libXML2 (dal repository della distribuzione), e poi compilare il programma con:
gcc serxml.c -I/usr/include/libxml2 -o serxml -lxml2Nel prossimo post (se non cambio idea nel frattempo) vedremo la funzione inversa, un XML Deserializer (e qui ci starebbe bene un "Ohhhhhhh" di stupore).
Ciao e al prossimo post!
lunedì 12 ottobre 2015
Il Client oscuro - Il ritorno
come scrivere un Socket Client in C
Oggi non avevo voglia di scervellarmi per trovare un titolo adatto al post per cui ho pensato di riciclare il titolo (e anche parte del testo) del post precedente, di cui questo è, evidentemente, il seguito (come promesso, eh!). Per chi non avesse letto la prima parte... vergogna! Subito a leggerla! Se no non capite di cosa stiamo parlando...
Come si intuisce dal titolo questa volta è il turno del Socket Client (o TCP Client, fa lo stesso). 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 Socket 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!
![]() |
anche Bane si chiede: non è un Blog di Cinema questo, neh? |
#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 9999]\n", argv[0], argv[0]); return EXIT_FAILURE; } // crea un socket int my_socket; if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { // errore socket() printf("%s: could not create 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 server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(argv[1]); server.sin_port = htons(atoi(argv[2])); // connessione al server remoto if (connect(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore connect() printf("%s: connect failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // 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 (send(my_socket, my_msg, strlen(my_msg), 0) < 0) { // errore send() printf("%s: send failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // riceve una risposta dal server remoto memset(my_msg, 0, MYBUFSIZE); if (recv(my_socket, my_msg, MYBUFSIZE, 0) < 0) { // errore recv() printf("%s: recv failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // mostra la risposta printf("%s: Server reply: %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 Socket Client:
- socket() - crea un socket
- prepara la struttura sockaddr_in per il server remoto
- connect() - connessione al server remoto
- send() + recv() - 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@linux-nb:~/blogtest$ ./socket_server 9999 ./socket_server: attesa connessioni entranti... ./socket_server: ricevuto messaggio dal sock 4: pippo ./socket_server: ricevuto messaggio dal sock 4: pluto ./socket_server: client disconnectedterminale 2 (Client):
aldo@linux-nb:~/blogtest$ ./socket_client 127.0.0.1 9999 Scrivi un messaggio per il Server remoto: pippo ./socket_client: Server reply: mi hai scritto: pippo Scrivi un messaggio per il Server remoto: pluto ./socket_client: Server reply: 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 se ne accorge. Missione compiuta!
Ciao e al prossimo post!
venerdì 4 settembre 2015
Il Server oscuro - Il ritorno
come scrivere un Socket Server in C
Un po' come Batman, sono tornato! Certo, lungi da me mettermi sullo stesso piano del mitico Cavaliere Oscuro, ma questo è il mio blog e qualche licenza posso permettermela, no?.
Come si intuisce dal titolo riprendiamo la nostra C-adventure parlando di Socket Server (o TCP Server, fa lo stesso). 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!
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 Socket 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 avevo 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 Client e, come sempre, non trattenete il respiro nell'attesa...
Ciao e al prossimo post!
![]() |
Non è un Blog di Cinema questo, neh? |
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <errno.h> #define BACKLOG 10 // per listen() #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 9999]\n", argv[0], argv[0]); return EXIT_FAILURE; } // crea un socket int my_socket; if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { // errore socket() printf("%s: could not create 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 server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(atoi(argv[1])); // bind informazioni del server al socket if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore bind() printf("%s: bind failed (%s)", argv[0], strerror(errno)); return EXIT_FAILURE; } // start ascolto con una coda di max BACKLOG connessioni if (listen(my_socket, BACKLOG) < 0) { // errore listen() printf("%s: listen failed (%s)\n", argv[0], strerror(errno)); 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() printf("%s: accept failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // loop di ricezione messaggi dal client int read_size; char client_msg[MYBUFSIZE]; while ((read_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); write(client_sock, server_msg, strlen(server_msg)); // clear buffer memset(client_msg, 0, MYBUFSIZE); } // loop terminato: test motivo if (read_size < 0) { // errore recv() printf("%s: recv failed\n", argv[0]); return EXIT_FAILURE; } else if (read_size == 0) { // Ok: il client si è disconnesso printf("%s: client disconnected\n", argv[0]); } // 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 Socket Server:
- socket() - crea un socket
- prepara la struttura sockaddr_in per questo server
- bind() - bind informazioni del server al socket
- listen() - start ascolto con una coda di max BACKLOG connessioni
- accept() - accetta connessioni da un client entrante
- recv() - loop di ricezione messaggi dal client
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 avevo 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 Client e, come sempre, non trattenete il respiro nell'attesa...
Ciao e al prossimo post!
Iscriviti a:
Post (Atom)