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

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!