Programación de Sockets en C: Servidor TCP Concurrente para Gestión de Clientes
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.