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...

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.
hoy vamos a hablar de los UDP client...
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á!
#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:
  1. socket() - crea un socket
  2. prepara la estructura sockaddr_in para el server remoto
  3. sendto() + recvfrom() - loop de comunicación col server remoto
por supuesto, hay variaciones de esta estructura, pero esta es la clásica. En este ejemplo, escrito y probado específicamente para el blog (bueno, en realidad he adaptado/modificado un poco de código que escribí para trabajo: ¡no es el primer UDP Client que escribo!), en el bucle de comunicación hay, también, la lectura de la respuesta del Server, por lo que cerramos el círculo con el post precedente y podemos probar en serio una conversación Client/Server.

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: pluto
terminal 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: ^C
Como 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.
Mr UDP, Mr TCP y la deslumbrante fotografía de una obra maestra
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:
...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
  1. socket() - crea un socket
  2. prepara la estructura sockaddr_in para este servidor
  3. bind() - bind informaciones del servidor al socket
  4. recvfrom() - bucle de recepción mensajes del cliente
En este ejemplo, escrito y probado específicamente para el blog, en el bucle de lectura se re-envía al Client el mensaje recibido. En el próximo post veremos el Client, por lo que cerraremos el círculo y podremos probar en serio una conversación UDP Client/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!

viernes, 3 de junio de 2016

Sprintf Driver
por qué no hay que usar la sprintf en C

"¿Hablas conmigo? ¿Me lo dices a mí?... Dime, ¿Es a mí?". Sí, al igual que el gran De Niro en Taxi Driver, esa fue mi reacción (incrédula) cuando descubrí (hace muchos años, ahora) que, después de años y años de uso honrado, tendría que dejar de utilizar la sprintf() .
...Y tu me dices de no utilizar la sprintf? A mí?...
Bueno, en realidad, si la usas bien, y tienes el 100% de control sobre el código escrito, también puede utilizarla sin grandes problemas, pero, como dicen los ingleses, la sprintf() es error prone, fácilmente te lleva a cometer errores, incluso graves. El problema más grave y evidente con la sprintf() se llama buffer overflow, y no creo que sea necesario gastar muchas palabras en eso: si el búfer que pasamos como primer argumento no es del tamaño correcto el desastre está detrás de la esquina.

Afortunadamente viene en nuestro auxilio la snprintf(), que es de la misma familia, pero más segura. Vemos los dos prototipos en comparación:
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
La snprintf() nos obliga a poner el size del buffer como segundo argumento, entonces es muy fácil coger la costumbre de escribir de una manera error-free como esta:
char buffer[32];
snprintf(buffer, sizeof(buffer), "Hello world!");
Si en lugar de "Hello world!" hubiéramos escrito una cadena de más de 32 chars, ningún problema: la snprintf() trunca la cadena de manera adecuada y estamos a salvo.
Y ahora os propongo un pequeño ejemplo real: tomamos una nuestra vieja conocida escrita para un viejo post, la getDateUsec() y la vamos a escribir en dos versiones, una buena y otra mala (bad). Veamos:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

// prototipos locales
char *getDateUsec(char *dest, size_t size);
char *badGetDateUsec(char *dest);

// función main
int main(int argc, char* argv[])
{
    // llama getDateUsec (o badGetDateUsec) y escribe el resultado
    char dest[12];
    printf("fecha con usec: %s\n", getDateUsec(dest, sizeof(dest)));
    //printf("fecha con usec: %s\n", badGetDateUsec(dest));

    return EXIT_SUCCESS;
}

// getDateUsec() - Genera una cadena con fecha y hora (usa los microsegundos)
char *getDateUsec(char *dest, size_t size)
{
    // get time (con gettimeofday()+localtime() en lugar de time()+localtime() para obtener los usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format cadena destinación dest(debe ser alocada por el llamante) y añade los usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    snprintf(dest, size, fmt, tv.tv_usec);

    // return cadena destinacion dest
    return dest;
}

// badGetDateUsec() - Genera una cadena con fecha y hora (usa los microsegundos) (versione bad)
char *badGetDateUsec(char *dest)
{
    /// get time (con gettimeofday()+localtime() en lugar de time()+localtime() para obtener los usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format cadena destinación dest(debe ser alocada por el llamante) y añade los usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    sprintf(dest, fmt, tv.tv_usec);

    // return cadena destinacion dest
    return dest;
}
Vale, en aquel entonces, para simplificar, había escrito una getDateUsec() que era, de hecho, una badGetDateUsec() (y más tarde, por la precisión, procedí en modificarla en el post). Esa versión funcionaba, pero podía crear problemas, mientras que la nueva versión es mucho más segura. Intentad compilar el ejemplo, adonde, deliberadamente, he subdimensionado el buffer de destino: comentando badGetDateUsec() y usando la getDateUsec(), funciona perfectamente, truncando el output a 12 chars. Si, sin embargo, se comenta la getDateUsec() y se utiliza la badGetDateUsec() el programa peta mientras se ejecuta. !Inténtelo!

Y ya que estamos en el argumento sprintf() un pequeño consejo un poco OT: si necesitáis agregar secuencialmente una cadena (en un bucle, por ejemplo) sobre una cadena base (para redactar un texto, por ejemplo) no hacerlo nunca así:
char buf[256] = "";
for (int i = 0; i < 5; i++)
    sprintf(buf, "%s añadido a la cadena %d\n", buf, i);
el método aquí arriba parece funcionar, pero, en realidad, funciona cuando le da la gana. Hacerlo, en cambio, así:
char buf[256] = "";
for (int i = 0; i < 5; i++) {
    char tmpbuf[256];
    sprintf(tmpbuf, "%s añadido a la cadena %d\n", buf, i);
    sprintf(buf, "%s", tmpbuf);
}
Y si no me creéis probad a verificar el código con un lint como cppchek (que siempre es una buena idea) o consultad el manual de la 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).
Y, por supuesto, también en este último ejemplo (hecho, por simplicidad, con la sprintf ()) seria recomendable usar la snprintf ().

¡Hasta el próximo post!