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

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...
también Bane se pregunta: ¿No es un blog de cine, eh?
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á!
#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:
  1. socket() - crea un socket
  2. prepara la estructura sockaddr_in para el server remoto
  3. connect() - conexión al server remoto
  4. send() + recv() - loop di 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 Socket 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@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 disconnected
terminal 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: ^C
Como 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!