Programación de Sockets en C: Servidor TCP Concurrente para Gestión de Clientes

Informática y Telecomunicaciones6,55 KB

Introducción al Código del Servidor

A continuación se presenta la implementación en lenguaje C de un servidor basado en sockets TCP. Este programa gestiona conexiones de clientes, procesa información de usuarios y administra registros de viajes mediante estructuras de datos en memoria.

Código Fuente Implementado

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAX 200 

typedef struct{
    char usuario[20];
    int num_viajes;
} TInfoUsuario;

typedef struct {
    TInfoUsuario usuarios[MAX];
    int num_clientes;
} Tclientes;

Tclientes mi_lista;

int main(int argc, char *argv[])
{
    int nchar, sock, con, port, len, fd, remote_addr_len;
    int s, r;
    int i;
    struct sockaddr_in serv_adr, remote_addr;

    char buf[MAX];
    char buf2[MAX];
    char *codiop;
    char *p1;
    char *p2;
    char nombre[20];
    char operacion[50];
    int pos2 = 0;

    printf ("Empezamos creando el socket\n");

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        perror("socket");
        exit(-1);
    }
    printf ("Listo\n");

    /* Configuración del bind con INADDR_ANY */
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_port = htons(50001);
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);

    printf ("Ahora el bind\n");

    r = bind(s, (struct sockaddr *) &serv_adr, sizeof(serv_adr));
    if (r == -1) {
        perror("bind");
        exit(-1);
    }

    /* Limitamos el número de conexiones pendientes, la cola de espera */
    r = listen(s, 4);
    if (r == -1) {
        perror("listen");
        exit(-1);
    }

    /* Bucle infinito para la gestión de conexiones */
    for(;;) {
        printf ("Esperando conexión\n");
        remote_addr_len = sizeof(remote_addr);
        con = accept(s, (struct sockaddr *)&remote_addr, &remote_addr_len);
        printf ("Conexión de %s:%d\n", inet_ntoa(remote_addr.sin_addr), ntohs(remote_addr.sin_port));

        // Leemos el nombre del cliente
        nchar = read(con, buf, MAX); // nchar indica el número de caracteres recibidos
        buf[nchar] = '\0';
        codiop = strtok(buf, ":");
        if (codiop != NULL) {
            i = atoi(codiop);
        }

        printf("He recibido %s\n", buf);

        while(i != 3) {
            switch(i) {
                case 0: // FALTA: coger p1 y p2 y meterlo en la base de datos
                    p1 = strtok(NULL, ":");
                    if (p1 != NULL) {
                        strcpy(nombre, p1);
                    }
                    int b = 0;
                    strcpy(operacion, "Hola ");
                    strcat(operacion, nombre);
                    int x = 0;
                    while (x < mi_lista.num_clientes) {
                        if (strcmp(mi_lista.usuarios[x].usuario, nombre) == 0) { // Si son iguales
                            write(con, operacion, strlen(operacion));
                            pos2 = x; // Para utilizarla en las siguientes funciones
                            b = 1; // Para que no entre en el siguiente if
                            x = mi_lista.num_clientes; // Sale del while
                        }
                        x++; // Si no son iguales pasa a la siguiente posición
                    }
                    if (b == 0) { // Si no lo ha encontrado en la lista
                        strcpy(mi_lista.usuarios[mi_lista.num_clientes].usuario, nombre);
                        mi_lista.usuarios[mi_lista.num_clientes].num_viajes = 0; 
                        pos2 = mi_lista.num_clientes; 
                        mi_lista.num_clientes++; 
                        printf("%s", operacion);
                        write(con, operacion, strlen(operacion));
                    }
                    break;

                case 1:
                    p1 = strtok(NULL, ":");
                    int x1;
                    char viajes[20];
                    if (p1 != NULL) {
                        strcpy(viajes, p1);
                    }
                    int iviajes;
                    iviajes = atoi(viajes);
                    mi_lista.usuarios[pos2].num_viajes = mi_lista.usuarios[pos2].num_viajes + iviajes;
                    x1 = mi_lista.usuarios[pos2].num_viajes;
                    printf("El número de viajes: %d\n", x1);
                    sprintf(buf2, "El número de viajes en su tarjeta es: %d\n", x1);
                    write(con, buf2, strlen(buf2));
                    break;

                case 2:
                    if (0 < mi_lista.usuarios[pos2].num_viajes) {
                        mi_lista.usuarios[pos2].num_viajes--;
                        printf("El número de viajes del cliente son: %d\n", mi_lista.usuarios[pos2].num_viajes);
                        write(con, "Ha gastado un viaje", strlen("Ha gastado un viaje"));
                    } else {
                        write(con, "No le quedan viajes", strlen("No le quedan viajes"));
                    }
                    break;
            }

            nchar = read(con, buf, MAX); // nchar indica el número de caracteres recibidos
            buf[nchar] = '\0';
            codiop = strtok(buf, ":");
            if (codiop != NULL) {
                i = atoi(codiop);
            }
        }

        // FALTA: CONSULTAS

        write(con, "desconexión", strlen("desconexión"));
        printf ("Desconexión\n");
        close(con);
    }
    return 0;
}

Detalles Técnicos del Funcionamiento

Creación y Configuración del Socket

El programa utiliza la función socket() especificando el dominio AF_INET (IPv4) y el tipo SOCK_STREAM (orientado a conexión, TCP). Posteriormente, se enlaza a un puerto específico (50001) mediante bind() y se habilita la escucha de conexiones entrantes con listen().

Control de Flujo y Protocolo

Mediante un bucle infinito, el servidor acepta conexiones de los clientes utilizando accept(). Una vez establecida la comunicación, se procesan tramas de datos estructuradas con códigos de operación (switch-case):

  • Caso 0: Registro o validación de usuario existente.
  • Caso 1: Recarga o adición de viajes a la tarjeta del usuario.
  • Caso 2: Consumo de un viaje registrado.
  • Caso 3: Finalización de la sesión y cierre de la conexión.

Descargar

PDF Word HTML