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...
viernes, 23 de diciembre de 2016
jueves, 8 de diciembre de 2016
El último Apache III - La venganza cómo escribir un módulo Apache en C - pt.3
Entonces, hacemos el punto: en los últimos tres post hemos hablado de módulos lighttpd (aquí, aquí y también aquí). En el primero de los tres he mencionado que el tema no era nuevo: antiguamente había escrito dos post (aquí y aquí) que hablaban de módulos para Apache, que es un pariente cercano de lighttpd (familia Web Servers). Aquí, para cerrar el circulo os propongo una tercera entrega de "El último Apache" (¿o Mohicano?), con una (espero) interesante extensión de lo que se había escrito.
Pues bien, en "El último Apache II - El regreso" (que acabáis de releer, supongo...) escribimos un agradable módulo elemental para Apache. Hacia poco (escribía solo una presentación en browser), pero era un buen comienzo para entrar en el mundo de los módulos para Web Servers. Vale, ahora vamos a reanudar ese módulo y les vamos a añadir una función que extrae los datos POST incluidos en una eventual petición HTTP de tipo POST que llega al módulo. ¿Por qué he elegido añadir esa funcionalidad? Bueno, obviamente porque es bastante normal que un módulo cuente con varios tipos de peticiones (GET, POST, etc.), y luego, en particular, recuerdo que la primera vez que he añadido esta funcionalidad en un módulo Apache que escribí, me di cuenta de que no estaban disponibles muchas indicaciones sobre este tema (y esto a pesar de la enorme cantidad de documentación de Apache disponible respeto a la de lighttpd).
Pues bien, sin molestarse en repetir todo el código y la forma de generarlo (hay de todo en "El último Apache II - El regreso"), sólo reescribiremos la función myapmodHandler() y añadiremos una nueva función getPost(). ¡Vamos con el código!
la función original myapmodHandler() se ha convertido en una especie de función helloworld (bueno, en práctica lo era incluso antes), que distingue los tipos de petición y, en el caso POST, llama a la función readPost() y escribe como respuesta "Hello, world!" mas los datos. En el caso de peticiones GET o POST sin datos se limita a escribir "Hola, mundo!", y para otros tipos de peticiones sale con error.
La función readPost() es simple, pero no es inmediata: en su tiempo la derivé de el único ejemplo interesante y bien hecho que encontré, y que estaba en el (óptimo) libro "Writing Apache Modules with Perl and C", que os recomiendo. Yo diría que en la última versión de Apache, la 2.4, se han introducido (y descritos aquí) otros métodos de extracción de datos de POST (¡os deseo buena lectura!), de todas maneras yo me la arreglé así, y el módulo funcionaba (y funciona todavía) muy bien.
¡Ah, el test, se me olvidaba! Testear con peticiones POST no es simple cómo hacerlo con las GET, adonde es suficiente (como dicho en el post anterior) escribir la URI del modulo en la barra de direcciones de Firefox o Chrome y esperar la respuesta. Para probar el nuestro nuevo módulo es mejor usar un buen plugin (como Poster, por ejemplo) que nos facilitará mucho la tarea.
Vale, de momento, creo que podemos parar por un tiempo el tema módulos de Apache/lighttpd. Juro que en el próximo post voy a hablar de otra cosa, no me gustaría que pensaran que el C se utiliza sólo para Web Servers...
¡Hasta el próximo post!
...en realidad soy un Mohicano, no un Apache... |
Pues bien, sin molestarse en repetir todo el código y la forma de generarlo (hay de todo en "El último Apache II - El regreso"), sólo reescribiremos la función myapmodHandler() y añadiremos una nueva función getPost(). ¡Vamos con el código!
// handler del modulo static int myapmodHandler( request_rec *reqrec) // request data { // test handler if (! reqrec->handler || strcmp(reqrec->handler, "myapmod")) return DECLINED; // set del apropiado content type ap_set_content_type(reqrec, "text/html"); // test método http if (reqrec->method_number == M_GET) { // petición GET: escribe solo "Hello, World!" ap_rprintf(reqrec, "GET request: Hello, world!"); } else if (reqrec->method_number == M_POST) { // petición POST: lee POST data char *data; if (readPost(reqrec, &data) != -1) { // escribe "Hello, World!" y enseña POST data ap_rprintf(reqrec, "POST request: Hello, world! postdata: %s", data); } else { // POST data no disponible: escribe solo "Hello, World!" ap_rprintf(reqrec, "POST request: Hello, world!"); } } else return HTTP_METHOD_NOT_ALLOWED; // sale con OK return OK; } // función para leer POST data static int readPost( request_rec *reqrec, // request data char **data) // buffer de destinazion paraer POST data { // setup del client para permitir que Apache lea el request body if (ap_setup_client_block(reqrec, REQUEST_CHUNKED_ERROR) == OK) { // determina si el client ha enviado datos if (ap_should_client_block(reqrec)) { // lee los datos de 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 de inserción datos en el buffer de destinación 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 bloque de datos memcpy((char *)*data + rpos, argsbuffer, rsize); rpos += rsize; } // POST data leido: return OK return 0; } } // sale con NOK return -1; }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! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
la función original myapmodHandler() se ha convertido en una especie de función helloworld (bueno, en práctica lo era incluso antes), que distingue los tipos de petición y, en el caso POST, llama a la función readPost() y escribe como respuesta "Hello, world!" mas los datos. En el caso de peticiones GET o POST sin datos se limita a escribir "Hola, mundo!", y para otros tipos de peticiones sale con error.
La función readPost() es simple, pero no es inmediata: en su tiempo la derivé de el único ejemplo interesante y bien hecho que encontré, y que estaba en el (óptimo) libro "Writing Apache Modules with Perl and C", que os recomiendo. Yo diría que en la última versión de Apache, la 2.4, se han introducido (y descritos aquí) otros métodos de extracción de datos de POST (¡os deseo buena lectura!), de todas maneras yo me la arreglé así, y el módulo funcionaba (y funciona todavía) muy bien.
¡Ah, el test, se me olvidaba! Testear con peticiones POST no es simple cómo hacerlo con las GET, adonde es suficiente (como dicho en el post anterior) escribir la URI del modulo en la barra de direcciones de Firefox o Chrome y esperar la respuesta. Para probar el nuestro nuevo módulo es mejor usar un buen plugin (como Poster, por ejemplo) que nos facilitará mucho la tarea.
Vale, de momento, creo que podemos parar por un tiempo el tema módulos de Apache/lighttpd. Juro que en el próximo post voy a hablar de otra cosa, no me gustaría que pensaran que el C se utiliza sólo para Web Servers...
¡Hasta el próximo post!
domingo, 6 de noviembre de 2016
El gran lighttpd cómo escribir un módulo lighttpd en C - pt.3
El Nota: ¿Seguro que no le importará?
Bunny: A él no le importa nada. Es un nihilista.
El Nota: Eso debe ser agotador.
Sí, un poco agotados hemos llegado al último capítulo, hoy vamos a escribir el código de nuestro mini módulo lighttpd. Vaya, muy agotador para El Nota...
Pero antes veamos un momento cómo está estructurado un módulo. Aquí hay una pequeño esquema descriptivo (extracto de nuestro mod_helloworld.c creado en el post anterior: ¡volver inmediatamente a leer el post anterior!):
Entonces, ¿qué partes de nuestro módulo tenemos que cambiar para que, usándolo, nos escriba "Hello, world!" en el navegador? Dado que el problema es bastante simple, también el código no será muy largo y complejo: sólo tendremos que cambiar la callback mod_helloworld_uri_handler de la siguiente manera:
Algunas partes están idénticas a la callback original (pero he añadido algunos comentarios), y son típicas de la estructura del código lighttpd: por no complicar el asunto no hablaré de estas partes. Sin embargo, podéis confiar en ellas porque lighttpd está escrito y funciona como debería.
La parte realmente nueva che he escrito es la que empieza con el comentario "test handler (return si error)", donde se testea si el URI pasado al navegador contiene el nombre del módulo y, en ese caso, ejecuta lo que queremos: copia en un buffer el texto "Hello, world!" y lo envía como respuesta a la petición. Así que si escribimos en la barra de direcciones de Firefox, Chrome, Opera (o cualquier otro navegador menos IE, por favor...)
El código que acabo de enseñar lo he escrito y probado con lighttpd 1.4.33. Si utilizáis una versión más reciente (de la 1.4.36 en adelante) descubriréis que algo ha cambiado a nivel de gestión de buffer, por lo que, por ejemplo, las líneas:
¡Hasta el próximo post!
Bunny: A él no le importa nada. Es un nihilista.
El Nota: Eso debe ser agotador.
Sí, un poco agotados hemos llegado al último capítulo, hoy vamos a escribir el código de nuestro mini módulo lighttpd. Vaya, muy agotador para El Nota...
...estoy agotado... |
// 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; }Se puede notar el uso de unas macros (amablemente proporcionadas por el entorno de desarrollo lighttpd) para crear unas callback con las que inicializar una estructura plugin que luego lighttpd utilizará para gestionar el nuestro módulo cada vez que una petición HTTP pida de usarlo. Para aquellos que quieran profundizar el argumento callback ya ha sido ampliamente descrito aquí y aquí (supongo que ya habéis leído esos post... ¿o no?).
Entonces, ¿qué partes de nuestro módulo tenemos que cambiar para que, usándolo, nos escriba "Hello, world!" en el navegador? Dado que el problema es bastante simple, también el código no será muy largo y complejo: sólo tendremos que cambiar la callback mod_helloworld_uri_handler de la siguiente manera:
URIHANDLER_FUNC(mod_helloworld_uri_handler) { plugin_data *p = p_d; UNUSED(srv); // test modo (return si error) if (con->mode != DIRECT) return HANDLER_GO_ON; // test uri path (return si error) if (con->uri.path->used == 0) return HANDLER_GO_ON; mod_helloworld_patch_connection(srv, con, p); // test handler (return si error) if (con->uri.path->ptr && strstr(con->uri.path->ptr, "helloworld")) { // escribe 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 terminado return HANDLER_FINISHED; } else return HANDLER_GO_ON; }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.
Algunas partes están idénticas a la callback original (pero he añadido algunos comentarios), y son típicas de la estructura del código lighttpd: por no complicar el asunto no hablaré de estas partes. Sin embargo, podéis confiar en ellas porque lighttpd está escrito y funciona como debería.
La parte realmente nueva che he escrito es la que empieza con el comentario "test handler (return si error)", donde se testea si el URI pasado al navegador contiene el nombre del módulo y, en ese caso, ejecuta lo que queremos: copia en un buffer el texto "Hello, world!" y lo envía como respuesta a la petición. Así que si escribimos en la barra de direcciones de Firefox, Chrome, Opera (o cualquier otro navegador menos IE, por favor...)
http://127.0.0.1/helloworldtendremo como respuesta en el browser:
Hello, world!He escrito 127.0.0.1 (y se hubiera podido escribir localhost) porque, por supuesto, la primera prueba la haremos de forma local. Si luego queréis abrir vuestro equipo hacia el mundo exterior y utilizarlo como un Web Server real (en una red local o Internet) podéis repetir el test usando la vuestra dirección IP pública y obtendréis el mismo resultado. ¡El módulo funciona!
El código que acabo de enseñar lo he escrito y probado con lighttpd 1.4.33. Si utilizáis una versión más reciente (de la 1.4.36 en adelante) descubriréis que algo ha cambiado a nivel de gestión de buffer, por lo que, por ejemplo, las líneas:
if (con->uri.path->used == 0) ... buffer *buf = chunkqueue_get_append_buffer(con->write_queue);tienen que cambiar en:
if (buffer_is_empty(con->uri.path)) ... buffer *buf = buffer_init();pero estos son detalles. En futuro, según lo prometido, os propondré algún módulo más sofisticado, con cambios también en las otras callback, pero esto es suficiente por el momento, no quiero decir más de la cuenta, si no nos cansaremos como El Nota...
¡Hasta el próximo post!
martes, 11 de octubre de 2016
El gran lighttpd cómo escribir un módulo lighttpd en C - pt.2
El Nota: Sabes, esto... esto es un caso muy, muy complicado, Maude. Un montón de input y de output. Afortunadamente estoy siguiendo un régimen de drogas bastante estricto para mantener la mente..., ya sabes...: ágil.Entonces: la buena noticia es que para escribir un módulo lighttpd no estáis obligados a seguir el mismo régimen de El Nota... la mala noticia es que se necesita un poco de esfuerzo, pero será mucho más fácil después de leer este post (y el anterior, por supuesto... ¿que no lo habéis leído? ¡Pero esta es la segunda parte!).
cara de "¡Ahora se cómo se escribe un módulo lighttpd!" |
En primer lugar elegimos un nombre y una acción... va a ser un clásico: le llamaremos mod_helloworld y el módulo escribirá en el browser la frase "Hello, world!" (¡Muy, pero muy, original!).
Abrimos un terminal de Linux, entramos en la root-directory del entorno de desarrollo y ejecutamos los siguientes comandos (que he enumerado para describirlos uno por 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 restartEntonces: con 1. editamos (con gedit, pluma, vim, geany... o con lo que os gusta más), el file de configuración lighttpd para añadir a la lista de módulos instalados el nuestro nuevo módulo: buscad la lista "server.modules" y insertad después de el último módulo, el nuevo. Una vez echo esto el aspecto de la lista será así
server.modules = ( "mod_access", "mod_alias", "mod_compress", "mod_redirect", "mod_helloworld", )Con 2. editamos el file Makefile.am del package. En comparación con el package original estamos añadiendo un módulo, por lo que hay que buscar el último módulo estándar presente (normalmente es mod_accesslog). Nos encontraremos con cuatro líneas de este tipo:
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)Copiamos estas líneas y las pegamos inmediatamente a continuación, sustituiendo en las NUEVAS cuatro lineas mod_accesslog con mod_helloworld. Guardamos y salimos: ahora el nuestro Makefile.am está listo para manejar también el nuevo módulo. Las líneas añadidas serán estas:
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. copiamos el file mod_skeleton.c en un nuevo file che llamaremos mod_helloworld.c: mod_skeleton.c es untemplate file que contiene el esqueleto básico de un módulo: este file nos lo proporcionan amablemente los excelentes desarrolladores de lighttpd para facilitarnos el desarrollo de nuevos módulos.
Con 4. editamos mod_helloworld.c y, con un comando de sustitución global, cambiamos todas las recurrencias de la palabra skeleton en helloworld. Por el momento guardamos el file así como está y salimos del editor.
Con la secuencia 5.6.7.8. (¿ya vista en el post anterior, os acordáis?) compilamos e instalamos el nuevo módulo. Es posible que en el paso 6. (la configuración) el proceso os diga dice que no encontra algunas librerias: normalmente el error se resuelve usando:
sudo apt-get install libpcre3-dev libbz2-devy repitiendo, luego, el punto 6.
Con 9. copiamo el nuevo módulo generado (que tiene la forma de una librería dinámica, una .so) en la directory de sistema que contiene los módulos lighttpd.
Con 10., finalmente, reiniciamos lighttpd y, si no aparece ningún error, el nuestro Web Server ya está listo para utilizar el nuevo módulo (que, por el momento, está medio vacío: como se ha mencionado anteriormente es sólo el esqueleto de un módulo).
¿Que nos falta en este punto? Bueno, obviamente, tenemos que añadir un poco de código al nuestro módulo para que él haga algo (en este caso, mostrarnos un bonito "Hello, world!"). Pero esto lo veremos en el próximo episodio, y, como siempre, no contengáis la respiración esperando...
¡Hasta el próximo post!
domingo, 18 de septiembre de 2016
El gran lighttpd cómo escribir un módulo lighttpd en C - pt.1
Policía: ¿Y qué había en su maletín?Vale, a menos que no os interese hacer el mismo trabajo del legendario El Nota (The Dude), es una buena idea tener un know-how valioso. Este es un post de alto valor añadido, y lo es simplemente considerando la ley de la oferta y la demanda: vamos a hablar de un tema que no dispone de una base de conocimientos amplia, entonces esto es un post valioso.
El Nota: Eh, papeles, solo papeles. Ya saben, solo mis papeles. Papeles del trabajo.
Policía: ¿Y a qué se dedica, señor?
El Nota: Estoy desempleado.
...me gustaría escribir un módulo, pero estoy un poco cansado... |
Y llegamos a lighttpd: es un Web Server popular (pero mucho menos que Apache) y con características técnicas de primera clase: tiene un rendimiento comparable al de Apache, pero es mucho más ligero: poca carga de la CPU, bajo uso de memoria, etc. Prácticamente es un must para sistemas embeddeded o, más en general, para sistemas que necesitan alto rendimiento utilizando pocos recursos. Desafortunadamente lighttpd no tiene detrás una grande organización de desarrollo, y, en proporción a la menor popularidad, ni siquiera tiene un gran ejército de profesionales y aficionados que trabajan en ello. Gracias a esto NO se encuentra en la red una gran cantidad de documentación y guías, tanto oficiales que no. Escribir un módulo lighttpd no es fácil, y, con el escaso apoyo disponible, es una misión complicada.
En concreto: lighttpd es un gran producto escrito y mantenido por una pequeña comunidad de excelentes programadores, pero, al no tener detrás de una gran organización, se pierde un poco en los detalles: con más documentación y guías oficiales aumentaría la popularidad, lo que aumentaría también la comunidad de usuarios/desarrolladores (y por lo tanto la documentación no oficial), y, en definitiva, aumentaría el éxito del producto. Pero esto no pasa y entonces nos enfrentamos a un caso clásico de síndrome del perro que se muerde la cola.
lighttpd está en constante evolución, con rendimiento y fiabilidad en aumento, pero, por la falta de detalles mencionada antes, puede pasar que, entre una release y la otra, se olvide la retrocompatibilidad y se introduzcan cambios en la API de programación que invalidan muchos de los módulos (laboriosamente) escritos por desarrolladores externos (¡me pasó a mí con un módulo que escribí para la rel.1.4.33 y que con la rel.1.4.36 dejó de funcionar!).
Bueno, sin hablar más: vamos a escribir un módulo elemental para lighttpd, que será una buena base para escribir módulos más complejos (sobre los cuales os daré algunos consejos en futuros post). La primera actividad es, por supuesto, instalar lighttpd en el PC (y, si estaba instalado, quitar primero Apache). Buscando, con nuestro amigo Google, una de las muchas guías (como esta) para convertir un PC en un server LLMP (o WLMP) (no voy a describir el procedimiento para no alargar demasiado el discurso, pero, os lo garantizo, es bastante simple). Obviamente, al final del procedimiento hay que comprobar si el servidor Web funciona correctamente (por lo general las guías te dicen cómo hacerlo).
A continuación, hay que instalar lo necesario para desarrollar nuestro módulo. Las siguientes instrucciones son (por supuesto) para Linux. Para otros sistemas de la familia UNIX (BSD, OS X, etc.) el procedimiento se adapta de forma intuitiva, mientras que, por ese otro sistema que ni siquiera quiero mencionar (comienza en W...), lo siento pero tendréis que valeros vosotros mismos (si lo conoces lo evitas, y yo, os lo aseguro, lo conozco bien).
Entonces: descargaros de la Web oficial de lighttpd el tar de desarrollo correspondiente a la versión que acabais de instalar en el sistema (están disponibles también los tar de versiones anteriores a la última). Este tar sirve para crear en el sistema un entorno adecuado para el desarrollo y la instalación de nuevos módulos. Descomprimid (donde quieran) el tar, entren en la directory creada y intentad compilar e instalar la release (lo sé, es la misma versión que acabáis de instalar el sistema, pero este paso sirve para determinar si el sistema de desarrollo funciona correctamente). Suponiendo de utilizar la versión 1.4.33 escribiremos:
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 restartComprueben si el Web Server sigue funcionando (de la misma manera utilizada en la primera instalación) y, si todo está bien, estamos listos para escribir/compilar/instalar el nuestro nuevo módulo. Pero esto lo veremos en el próximo episodio...
¡Hasta el próximo post!
martes, 23 de agosto de 2016
UDPhunter cómo escribir un UDP Client en C - pt.2
Estamos al final de Agosto y la vuelta al cole es siempre dura. Entonces, por ahorrar tiempo y fatiga, intentaré reciclar parte del texto de un mi viejo post bien conectado con este. Hay que considerar que esta es la segunda parte (el Client) de los post sobre UDP y, entonces, la segunda parte de mis viejos post sobre TCP cae perfecta. El código es, obviamente, todo nuevo.
Como se intuye por el título esta vez vamos a hablar de UDP Client. 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á!
La estructura es la clásica y básica de un UDP 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!
hoy vamos a hablar de los 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 argumentos if (argc != 3) { // error args printf("%s: numero argumentos equivocado\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) { // error socket() printf("%s: no puedo crear el 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 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 comunicación con el server remoto for (;;) { // compone mensaje para el server remoto char my_msg[MYBUFSIZE]; printf("Escribe un mensaje para el Server remoto: "); scanf("%s", my_msg); // send mensaje al server remoto if (sendto(my_socket, my_msg, strlen(my_msg), 0, (struct sockaddr *)&server, sizeof(server)) < 0) { // error send() printf("%s: error send (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // recibe una respuesta del server remoto memset(my_msg, 0, MYBUFSIZE); if (recvfrom(my_socket, my_msg, MYBUFSIZE, 0, NULL, NULL) < 0) { // errore recv() printf("%s: error recv (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // enseña la respuesta printf("%s: respuesta Server: %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! ¡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 UDP Client:
- socket() - crea un socket
- prepara la estructura sockaddr_in para el server remoto
- sendto() + recvfrom() - loop de 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@ao-linux-nb:~/blogtest$ ./udpserver 8888 ./udpserver: recibido mensaje del sock 3: pippo ./udpserver: recibido mensaje del sock 3: plutoterminal 2 (Client):
aldo@ao-linux-nb:~/blogtest$ ./udpclient 127.0.0.1 8888 Escribe un mensaje para el Server remoto: pippo ./udpclient: respuesta Server: me has escrito: pippo Escribe un mensaje para el Server remoto: pluto ./udpclient: respuesta Server: 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 no se da cuenta (es una comunicación connectionless). ¡Mission cumplida!
¡Hasta el próximo post!
sábado, 9 de julio de 2016
UDPhunter cómo escribir un UDP Server en C - pt.1
A menudo se puede hacer lo mismo en dos (o tal vez 1000...) maneras diferentes. No necesariamente todas buenas. Por ejemplo, de una buena novela como El dragón rojo se han echo dos películas: una es una obra maestra de Michael Mann, mientras que la otra es una porquería que hubiera sido mejor no rodar.
Cuando pensamos en las comunicaciones basadas en los Socket siempre pensamos en los TCP Client/Server, y nos olvidamos de la existencia de UDP. Es que, con UDP, se pueden hacer cosas excelentes, siempre teniendo en cuenta lo que dice el manual:
Por eso os propongo un UDP Server que es casi una copia del TCP Server visto aquí, y que ya a primera vista se nota más sencillo. ¡Vamos con el código!
La estructura es la clásica y básica de un UDP Server:
Como (espero) se habrán dado cuenta, en lugar de las funciones de send/recv se usan sendto/recvfrom (que son idénticas a las homólogas del TCP pero añaden los datos de los sender/receiver) y, ademas, faltan las fases de listen e accept, lo que hace que el Server sea más fácil de escribir y, también, de usar: se pueden hacer secuencias de start/stop/restart de Server y Client en el orden que desee y la comunicación, mágicamente, siempre se restaura, mientras que la versión TCP (supongo que ya la habéis probado, y si no: ¡correr a hacerlo!) que tiene una fase de conexión, requiere una secuencia más rigurosa de start/stop para funcionar.
Nos vemos para el UDP Client y, como siempre, no contengáis la respiración esperando...
¡Hasta el próximo post!
Mr UDP, Mr TCP y la deslumbrante fotografía de una obra maestra |
...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...No nos dejemos engañar por la (preocupante) palabra unreliable: si lo utilizamos en las aplicaciones adecuadas (aquellas donde la pérdida/duplicación de un paquete no es un error trágico... el VoIP, por ejemplo) las cualidades de UDP (velocidad, ligereza, facilidad de implementación) se harán evidentes de inmediato.
Por eso os propongo un UDP Server que es casi una copia del TCP Server visto aquí, y que ya a primera vista se nota más sencillo. ¡Vamos con el código!
#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 != 2) { // error args printf("%s: numero argumentos equivocado\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) { // error socket() printf("%s: no puedo crear el socket (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // prepara la estructura sockaddr_in para este 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 informaciones del server al socket if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // error bind() printf("%s: error bind (%s)", argv[0], strerror(errno)); return EXIT_FAILURE; } // loop de recepcion mensajes del client struct sockaddr_in client; // buffer para datos del client para poder contestar 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: recibido mensaje del sock %d: %s\n", argv[0], my_socket, client_msg); char server_msg[MYBUFSIZE]; sprintf(server_msg, "me has escrito: %s", client_msg); if (sendto(my_socket, server_msg, strlen(server_msg), 0, (struct sockaddr *)&client, sizeof(client)) < 0) { // errore send() printf("%s: error send (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // clear buffer memset(client_msg, 0, MYBUFSIZE); } // error recv(): el "client disconnected" con recv_size==0 no existe porque este server es connectionless printf("%s: error recv (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; }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 UDP Server:
- socket() - crea un socket
- prepara la estructura sockaddr_in para este servidor
- bind() - bind informaciones del servidor al socket
- recvfrom() - bucle de recepción mensajes del cliente
Como (espero) se habrán dado cuenta, en lugar de las funciones de send/recv se usan sendto/recvfrom (que son idénticas a las homólogas del TCP pero añaden los datos de los sender/receiver) y, ademas, faltan las fases de listen e accept, lo que hace que el Server sea más fácil de escribir y, también, de usar: se pueden hacer secuencias de start/stop/restart de Server y Client en el orden que desee y la comunicación, mágicamente, siempre se restaura, mientras que la versión TCP (supongo que ya la habéis probado, y si no: ¡correr a hacerlo!) que tiene una fase de conexión, requiere una secuencia más rigurosa de start/stop para funcionar.
Nos vemos para el UDP Client y, como siempre, no contengáis la respiración esperando...
¡Hasta el próximo post!
Suscribirse a:
Entradas (Atom)