Escribir software es un placer. Un programa no sólo debe funcionar bien y ser eficiente (esto se da por supuesto), sino que también debe ser bello y elegante de leer, comprensible y fácil de mantener, tanto para el autor como para eventuales futuros lectores. Programar bien en C es un arte.
En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...
Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...
jueves, 24 de diciembre de 2015
lunes, 7 de diciembre de 2015
Mission: Impossible - XML Deserializer cómo escribir un XML Deserializer en C
Según lo prometido, volvemos con una nueva misión (no mucho) imposible para proponer una Deserializer complementario al Serializer del último post. Obviamente, si todavía no habéis leído el post anterior, deberíais:
1) avergonzaros
2) ir a leerlo. ¡Inmediatamente!
Ahora, si estáis leyendo esto, puede ser que ya habéis pasado los dos puntos anteriores, entonces podemos continuar. Vamos a utilizar también esta vez la libXML2 para escribir una función que consigue extraer todas las parejas key-value que componen un documento XML.
¡Vamos con el código!
1) codigo que se auto-explica, ampliamente comentado y... los comentarios hablan por sí mismos.
2) la función main() no hace más que arrancar la función deserialize(): concéntrese en esa.
La función, en realidad, está dividida en dos partes: deserialize() prepara la lectura del documento y recurDoc() (que es una función recursiva que se llama a si misma) recurre el documento n-veces hasta que haya campos para leer.
Este ejemplo es apto para leer cualquier archivo XML que se le pasa como argumento, y se puede especializar fácilmente, por ejemplo si les pasais un file de estructura conocida (por ejemplo, se puede intentar con el catálogo XML de películas producidas por nuestro anterior Serializer) podeis rellenar los campos con lo campos key-value de la estructura de datos correspondiente (en nuestro caso la struct Catalog). En pocas palabras, con un poco de imaginación, la pareja Serializer/Deserializer que os he propuesto, permite una gran cantidad de actividades interesantes en programas que utilizan datos XML. Buen trabajo...
Os recuerdo una vez más que, para probar el programa en Linux (lo cual hice, por supuesto...) se debe instalar antes la libXML2 (desde el repository de la distribución), y luego compilar el programa con:
1) avergonzaros
2) ir a leerlo. ¡Inmediatamente!
Ahora, si estáis leyendo esto, puede ser que ya habéis pasado los dos puntos anteriores, entonces podemos continuar. Vamos a utilizar también esta vez la libXML2 para escribir una función que consigue extraer todas las parejas key-value que componen un documento XML.
Siempre más fácil... |
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <libxml/parser.h> // prototipos locales void deserialize(const char* document); void recurDoc(xmlDocPtr doc, xmlNodePtr cur); // main del programa de test int main(int argc, char **argv) { // test argumentos if (argc <= 1) { printf("Usage: %s docname\n", argv[0]); return(0); } /* libxml2: inicializa la librería y testea potenciales ABI mismatches entre la versión compilada y la actual shared library usada */ LIBXML_TEST_VERSION; // deserializa XML deserialize(argv[1]); // sale con Ok return EXIT_SUCCESS; } // función deserialize() void deserialize( const char* document) { // elimina los blank nodes xmlKeepBlanksDefault(0); // extrae el documento del file xmlDocPtr doc; if ((doc = xmlParseFile(document)) == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return; } // test si el documento non está vacio xmlNodePtr cur; if ((cur = xmlDocGetRootElement(doc)) == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return; } // recurre y libera el documento recurDoc(doc, cur); xmlFreeDoc(doc); } // función recursiva de lectura del documento void recurDoc( xmlDocPtr doc, xmlNodePtr cur) { // loop de lectura xmlChar *value; cur = cur->xmlChildrenNode; while (cur != NULL) { // extrae y libera un elemento if ((value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) { printf("key: %-10s - value: %s\n", cur->name, value); xmlFree(value); } // llamada recursiva recurDoc(doc, cur); // pasa al próximo elemento cur = cur->next; } // sale return; }Y, como siempre:
1) codigo que se auto-explica, ampliamente comentado y... los comentarios hablan por sí mismos.
2) la función main() no hace más que arrancar la función deserialize(): concéntrese en esa.
La función, en realidad, está dividida en dos partes: deserialize() prepara la lectura del documento y recurDoc() (que es una función recursiva que se llama a si misma) recurre el documento n-veces hasta que haya campos para leer.
Este ejemplo es apto para leer cualquier archivo XML que se le pasa como argumento, y se puede especializar fácilmente, por ejemplo si les pasais un file de estructura conocida (por ejemplo, se puede intentar con el catálogo XML de películas producidas por nuestro anterior Serializer) podeis rellenar los campos con lo campos key-value de la estructura de datos correspondiente (en nuestro caso la struct Catalog). En pocas palabras, con un poco de imaginación, la pareja Serializer/Deserializer que os he propuesto, permite una gran cantidad de actividades interesantes en programas que utilizan datos XML. Buen trabajo...
Os recuerdo una vez más que, para probar el programa en Linux (lo cual hice, por supuesto...) se debe instalar antes la libXML2 (desde el repository de la distribución), y luego compilar el programa con:
gcc desxml.c -I/usr/include/libxml2 -o desxml -lxml2¡Hasta el próximo post!
sábado, 28 de noviembre de 2015
Mission: Impossible - XML Serializer cómo escribir un XML Serializer en C
La misión de hoy, en realidad, no es en absoluto imposible: escribir un XML Serializer en C, sobre todo con la ayuda de una excelente biblioteca open-source como libXML2 (licencia MIT), es relativamente simple. ¿Qué es un serializador? Es simplemente una función que nos permite transformar una estructura de datos, interna a un programa, en su equivalente en XML.
Vamos al grano: supongamos de tener la siguiente estructura de datos (de hecho, una estructura anidada, para complicarnos un poco la vida):
El main() es solo un ejemplo funcional: inicializa la librería (¡leer los comentarios!), prepara los datos para el test, los pasa al Serializer, escribe los resultados en un file.
Lo que importa es la función serialize(), que puede utilizarse dónde y cómo se quiere en un programa que utiliza las estructuras de datos descritas anteriormente, y que, sobre todo, se puede utilizar como un ejemplo para escribir funciones similares para otras estructuras de datos: es muy simple y se puede adaptar fácilmente a cualquier otro caso. Como se puede ver utiliza diversas funciones de biblioteca libXML2 y el mecanismo que le permite llenar el documento de destino es simple: crea un nodo inicial (root-node) y le agrega los hijos (child-nodes) a raíz de la forma de la estructura de datos original.
Me olvidaba: para probar el programa en Linux (lo cual hice, por supuesto...) se debe instalar antes la libXML2 (desde el repository de la distribución), y luego compilar el programa con:
¡Hasta el próximo post!
y bien que no era imposible... |
// tipo Film para test typedef struct { char title[32]; char director[32]; int year; } Film; // tipo Catalog para test typedef struct { time_t t_lastupd; Film films[2]; } Catalog;en un programa podemos llenarla con un poco de datos (beh, en este caso unos pocos, es sólo un ejemplo) y con nuestro XML Serializer tratar de conseguir un documento XML como esto:
<?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>Bueno, ya que estamos ansiosos por saber cómo hacerlo, vamos directamente al código:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <libxml/parser.h> // tipo Film para test typedef struct { char title[32]; char director[32]; int year; } Film; // tipo Catalog para test typedef struct { time_t t_lastupd; Film films[2]; } Catalog; // prototipos locales static char* serialize(char* document, const Catalog* catalog); // main() del programa de test int main(int argc, char* argv[]) { // test argumentos 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: inicializa la librería y testea potenciales ABI mismatches entre la versión compilada y la actual shared library usada */ LIBXML_TEST_VERSION; // prepara datos para 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; // serializa XML serialize(dest_document_xml, &catalog); // escribe los resultados en un file FILE* fp = fopen("catalog.xml", "w"); fprintf(fp, "%s", dest_document_xml); fclose(fp); // sale con Ok return EXIT_SUCCESS; } // función serialize() static char* serialize( char* document, // documento destino serializato const Catalog* catalog) // estructura 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); // añade <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 para añadir los child-nodes <FILM> y child-fields al nodo <CATALOG> int i; for (i = 0; i < sizeof(catalog->films) / sizeof(Film); i++) { // añade el child-node <FILM> al nodo <CATALOG> xmlNodePtr film_node = xmlNewChild(root_node, NULL, BAD_CAST "FILM", BAD_CAST NULL); // añade los 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 el document al documento destino xmlChar *xmlbuff; int buffersize; xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1); strcpy(document, (char *)xmlbuff); // libera recursos xmlFree(xmlbuff); xmlFreeDoc(doc); xmlCleanupParser(); // return un pointer al document formateado return document; }Ok, como se nota es (como siempre en este blog) ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciónes y/o grupos de instrucciones (¡leer los comentarios! ¡Estan ahi para eso!), pero voy a añadir, solamente (como de costumbre), algunos detalles estructurales.
El main() es solo un ejemplo funcional: inicializa la librería (¡leer los comentarios!), prepara los datos para el test, los pasa al Serializer, escribe los resultados en un file.
Lo que importa es la función serialize(), que puede utilizarse dónde y cómo se quiere en un programa que utiliza las estructuras de datos descritas anteriormente, y que, sobre todo, se puede utilizar como un ejemplo para escribir funciones similares para otras estructuras de datos: es muy simple y se puede adaptar fácilmente a cualquier otro caso. Como se puede ver utiliza diversas funciones de biblioteca libXML2 y el mecanismo que le permite llenar el documento de destino es simple: crea un nodo inicial (root-node) y le agrega los hijos (child-nodes) a raíz de la forma de la estructura de datos original.
Me olvidaba: para probar el programa en Linux (lo cual hice, por supuesto...) se debe instalar antes la libXML2 (desde el repository de la distribución), y luego compilar el programa con:
gcc serxml.c -I/usr/include/libxml2 -o serxml -lxml2En el próximo post (si no cambio de idea mientras tanto) veremos la función inversa, un XML Deserializer (y aquí estaría bien un "Ohhhhhhh" de sorpresa).
¡Hasta el próximo post!
lunes, 12 de octubre de 2015
El Client oscuro: la leyenda renace cómo escribir un Socket Client en C
Hoy yo no quería volverme loco para encontrar un título adecuado para el post, entonces he pensado de reciclar el título (y también parte del texto) de el post anterior, de el cual este es, obviamente, la secuela (como se había prometido, eh!). Para aquellos que no han leído la primera parte... ¡Qué verguenza! ¡Leerla ya! Si no no vais a entender de que estamos hablando...
Como se intuye por el título esta vez vamos a hablar de Socket Client (o TCP Client, da igual). Espero que todos sepan lo que es, de lo contrario os recomiendo una util lectura con ejemplo incluido (¡pero mi ejemplo es mejor!), así que no pierdo tiempo y puedo ir directamente al código. ¡Ahí está!
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciónes y/o grupos de instrucciones (¡leer los comentarios! ¡Estan ahi para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
la estructura es la clásica y básica de un Socket Client:
En cuanto al flujo y el estilo del main() son válidas las notas que figuran en el post sobre el Server. Para probarlo, basta con abrir dos terminales (UNIX o Linux, por supuesto), y iniciar el Server en uno y el Client en el otro; si usamos un solo ordenador el Client debe, como es lógico, conectarse con el Server en localhost. Para el argumento port se puede utilizar cualquier número elegido entre los no reservados (¡y hay que utilizar el mismo puerto para Client y Server!) El resultado es el siguiente:
terminal 1 (Server):
¡Hasta el próximo post!
también Bane se pregunta: ¿No es un blog de cine, eh? |
#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 argumentos if (argc != 3) { // errore args printf("%s: numero argumentos equivocado\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 estructura sockaddr_in para el 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])); // conexión 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 de comunicación col server remoto for (;;) { // compone mensaje para el server remoto char my_msg[MYBUFSIZE]; printf("Escibe un mensaje para el Server remoto: "); scanf("%s", my_msg); // send mensaje al server remoto if (send(my_socket, my_msg, strlen(my_msg), 0) < 0) { // error send() printf("%s: send failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // recibe una respuesta del 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; } // enseña la respuesta printf("%s: Server reply: %s\n", argv[0], my_msg); } // sale con Ok return EXIT_SUCCESS; }
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciónes y/o grupos de instrucciones (¡leer los comentarios! ¡Estan ahi para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
la estructura es la clásica y básica de un Socket Client:
- socket() - crea un socket
- prepara la estructura sockaddr_in para el server remoto
- connect() - conexión al server remoto
- send() + recv() - loop di comunicación col server remoto
En cuanto al flujo y el estilo del main() son válidas las notas que figuran en el post sobre el Server. Para probarlo, basta con abrir dos terminales (UNIX o Linux, por supuesto), y iniciar el Server en uno y el Client en el otro; si usamos un solo ordenador el Client debe, como es lógico, conectarse con el Server en localhost. Para el argumento port se puede utilizar cualquier número elegido entre los no reservados (¡y hay que utilizar el mismo puerto para Client y Server!) El resultado es el siguiente:
terminal 1 (Server):
aldo@linux-nb:~/blogtest$ ./socket_server 9999 ./socket_server: espera conexiones entrantes... ./socket_server: recibido mensaje del sock 4: pippo ./socket_server: recibido mensaje del sock 4: pluto ./socket_server: client disconnectedterminal 2 (Client):
aldo@linux-nb:~/blogtest$ ./socket_client 127.0.0.1 9999 Escribe un mensaje para el Server remoto: pippo ./socket_client: Server reply: me has escrito: pippo Escribe un mensaje para el Server remoto: pluto ./socket_client: Server reply: me has escrito: pluto Escribe un mensaje para el Server remoto: ^CComo se nota el Client y el Server se hablan y cuando el Client se desconecta (con un brutal Ctrl-C) el Server se da cuenta. ¡Mission cumplida!
¡Hasta el próximo post!
domingo, 6 de septiembre de 2015
El Server oscuro: la leyenda renace cómo escribir un Socket Server en C
Un poco como Batman, estoy de vuelta! Por supuesto, no es mi intención ponerme en el mismo nivel del legendario caballero oscuro, pero este es mi blog y me puedo permitir alguna licencia, ¿no?.
Como se intuye por el título reanudamos nuestra C-adventure hablando de Socket Server (o TCP Server, da igual). Espero que todos sepan lo que es, de lo contrario os recomiendo una util lectura con ejemplo incluido (¡pero mi ejemplo es mejor!), así que no pierdo tiempo y puedo ir directamente al código. ¡Ahí está!
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciones y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
la estructura es la clásica y básica de un Socket Server:
Notar que, además, el main() comienza con una prueba de los argumentos con anexo ejemplo de uso en caso de error: esto le da al código un aire profesional que no puede faltar en una aplicación de este tipo.
Desde el punto de vista del estilo, he escrito este código sin respetar mi estilo favorito (hay múltiples puntos de salida) pero para un programa casi-secuencial como esto no es un estilo despreciable, y además... ¡me ha venido así! Por otra parte en su momento dije que se necesita un poco de elasticidad, que no hay que ser demasiado rígidos.
Un último detalle sobre la #define BACKLOG: encontrar el valor apropiado es un tema casi filosófico, así que os remito a una buena descripción que he encontrado en la red. Nos vemos para el cliente y, como siempre, no contengáis la respiración esperando...
¡Hasta el próximo post!
No es un blog de cine, ¿eh? |
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <errno.h> #define BACKLOG 10 // para listen() #define MYBUFSIZE 1024 int main(int argc, char *argv[]) { // test argumentos if (argc != 2) { // error args printf("%s: numero argumentos equivocado\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) { // error socket() printf("%s: could not create socket (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // prepara la struct sockaddr_in para este 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 informaciones del server al socket if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // error bind() printf("%s: bind failed (%s)", argv[0], strerror(errno)); return EXIT_FAILURE; } // start escucha con una cola de max BACKLOG conexiones if (listen(my_socket, BACKLOG) < 0) { // error listen() printf("%s: listen failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // acepta conexiones de un client entrante printf("%s: espera connexiones entrantes...\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) { // error 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: recibido mensaje del sock %d: %s\n", argv[0], client_sock, client_msg); char server_msg[MYBUFSIZE]; sprintf(server_msg, "me has escrito: %s", client_msg); write(client_sock, server_msg, strlen(server_msg)); // clear buffer memset(client_msg, 0, MYBUFSIZE); } // loop terminado: test motivo if (read_size < 0) { // error recv() printf("%s: recv failed\n", argv[0]); return EXIT_FAILURE; } else if (read_size == 0) { // Ok: el client se ah desconectado printf("%s: client disconnected\n", argv[0]); } // salgo con Ok return EXIT_SUCCESS; }
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciones y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
la estructura es la clásica y básica de un Socket Server:
- socket() - crea un socket
- prepara la estructura sockaddr_in para este servidor
- bind() - bind informaciones del servidor al socket
- listen() - empieza a escuchar con una cola de max BACKLOG conexiones
- accept() - acepta conexiones entrantes de un cliente
- recv() - bucle de recepción mensajes del cliente
Notar que, además, el main() comienza con una prueba de los argumentos con anexo ejemplo de uso en caso de error: esto le da al código un aire profesional que no puede faltar en una aplicación de este tipo.
Desde el punto de vista del estilo, he escrito este código sin respetar mi estilo favorito (hay múltiples puntos de salida) pero para un programa casi-secuencial como esto no es un estilo despreciable, y además... ¡me ha venido así! Por otra parte en su momento dije que se necesita un poco de elasticidad, que no hay que ser demasiado rígidos.
Un último detalle sobre la #define BACKLOG: encontrar el valor apropiado es un tema casi filosófico, así que os remito a una buena descripción que he encontrado en la red. Nos vemos para el cliente y, como siempre, no contengáis la respiración esperando...
¡Hasta el próximo post!
Suscribirse a:
Entradas (Atom)