Programación de Sockets y Threads en C: Servidor de Votaciones, Chat y Gestión de Clientes Conectados
Servidor concurrente en C: votaciones, clientes conectados y chat
Este documento describe la lógica de un servidor concurrente implementado en lenguaje C mediante sockets y hilos POSIX (pthread). El servidor atiende a varios clientes simultáneamente, gestiona un sistema de votaciones, mantiene la lista de clientes conectados y reenvía mensajes entre ellos.
1. Procesamiento del código de votación
Cuando el código recibido es 1, el servidor acumula los votos de un partido político:
if (codigo == 1) // acumular votos
{
// recupero el nombre del partido
p = strtok( NULL, "/");
char nombre[20];
strcpy (nombre, p);
// recupero los votos
p = strtok( NULL, "/");
int votos = atoi (p);
// acumulamos votos
pthread_mutex_lock( &mutex );
AcumularVotos (&listaPartidos, nombre, votos);
pthread_mutex_unlock( &mutex);
// Ahora notifico a todos los conectados el nombre del partido que gana en este momento
char ganador [20];
strcpy (ganador, DameGanador (&listaPartidos));
sprintf (respuesta, "2/%s,", ganador);
int j;
for (j=0; j<i; j++)
write (sockets[j], respuesta, strlen(respuesta));
}
Consulta de votos de un partido
Con el código 2, el cliente solicita los votos que lleva un partido:
else if (codigo == 2) // nos piden los votos que lleva un partido
{
// recupero el nombre del partido
p = strtok( NULL, "/");
char nombre[20];
strcpy (nombre, p);
int votos = DameVotos (&listaPartidos, nombre);
sprintf (respuesta, "1/%d,", votos);
write (sock_conn, respuesta, strlen(respuesta));
}
else if (codigo == 0) // desconexión
fin = 1;
2. Consulta de clientes conectados
Este bloque atiende las peticiones relacionadas con los clientes conectados:
if (codigo == 1) // El cliente nos pide el anterior conectado
{
Nombre anterior;
strcpy (anterior, DameAnterior (&conectados, nombre));
sprintf (respuesta, "1/%s,", anterior);
write (sock_conn, respuesta, strlen(respuesta));
}
Número de clientes conectados
else if (codigo == 2) // Nos pide el número de conectados
{
sprintf (respuesta, "2/%d,", conectados.num);
write (sock_conn, respuesta, strlen(respuesta));
}
else if (codigo == 0) // desconexión
{
// Enviamos a todos el nombre del cliente que se desconecta
sprintf (respuesta, "3/%s,", nombre);
int j;
for (j=0; j<i; j++)
write (sockets[j], respuesta, strlen(respuesta));
fin = 1;
}
3. Protocolo de mensajes del servicio de chat
El servidor implementa los siguientes códigos de mensaje:
- Reenvío de frases: recibe una frase de un cliente y la reenvía a todos los clientes activos.
Mensaje recibido:1/Frase
No envía ninguna respuesta al cliente que envía la frase. - Desactivar cliente: marca a un cliente como no activo; a partir de ese momento no recibirá las frases de los demás.
Mensaje recibido:2/
No hay respuesta. - Activar cliente: marca de nuevo a un cliente como activo.
Mensaje recibido:3/
No hay respuesta. - Desconexión:
Mensaje recibido:0/
No hay respuesta.
El servidor también envía notificaciones: cuando recibe la frase de un cliente, la reenvía a todos los clientes activos en ese momento. El mensaje es directamente la frase, sin código, porque es el único tipo de mensaje que el servidor envía a los clientes.
4. Variables globales y estructuras de control
int sockets [100]; // vector para guardar los sockets
int estado [100]; // vector para guardar el estado de cada cliente (1 es activo y 0 es no activo)
int num;
Procesamiento de los mensajes del chat
if (codigo == 1) // El cliente envía una frase
{
// recupero la frase
p = strtok( NULL, "/");
char frase[100];
strcpy (frase, p);
// Ahora envío la frase a todos los clientes activos (menos al que envía la frase)
int j = 0;
// Envío a todos
for (j=0; j<num; j++)
if ((j != i) && (estado[j] == 1))
write (sockets[j], frase, strlen(frase));
}
else if (codigo == 2) // pasamos al cliente a estado no activo
{
pthread_mutex_lock( &mutex);
estado[i] = 0;
pthread_mutex_unlock( &mutex);
}
else if (codigo == 3) // pasamos al cliente a estado activo
{
pthread_mutex_lock( &mutex);
estado[i] = 1;
pthread_mutex_unlock( &mutex);
}
else // desconexión
{
// Aprovecho para marcar al cliente que se desconecta como no activo
pthread_mutex_lock( &mutex);
estado[i] = 0;
pthread_mutex_unlock( &mutex);
fin = 1;
}
5. Función principal (MAIN): aceptación de clientes
Cada vez que se acepta una nueva conexión, el servidor registra el socket, marca al cliente como activo y crea un hilo dedicado para atenderlo:
pthread_mutex_lock( &mutex);
sockets[num] = sock_conn;
estado[num] = 1; // inicialmente el cliente está activo
pthread_mutex_unlock( &mutex);
indices[num] = num;
pthread_create(&thread[num], NULL, AtenderCliente, &indices[num]);
num = num + 1;
6. Registro de nuevos conectados y mensajes de felicitación
Alta de un nuevo cliente
Con el código 1, el servidor recibe los datos del nuevo cliente con el formato 1/nombre/año/ciudad:
if (codigo == 1) // Recibo datos del nuevo conectado ("1/nombre/año/ciudad")
{
p = strtok( NULL, "/");
strcpy (nombre, p);
p = strtok( NULL, "/");
ano = atoi (p);
p = strtok( NULL, "/");
strcpy (ciudad, p);
pthread_mutex_lock( &mutex );
PonConectado (&miLista, nombre, ciudad, ano, sock_conn);
pthread_mutex_unlock( &mutex);
}
Envío de felicitaciones
Los códigos 2 y 3 permiten enviar una felicitación a los clientes del mismo año (código 2) o de la misma ciudad (código 3):
else if ((codigo == 2) || (codigo == 3))
{
char felicitacion[100];
p = strtok( NULL, "/");
strcpy (felicitacion, p);
// preparo la notificación
sprintf (notificacion, "%s quiere felicitarte con el siguiente mensaje: %s", nombre, felicitacion);
int i;
if (codigo == 2) // felicitamos a los del mismo año
{
for (i=0; i<miLista.num; i++)
if (miLista.conectados[i].ano == ano)
write (miLista.conectados[i].socket, notificacion, strlen(notificacion));
}
else // felicitamos a los de la misma ciudad
{
for (i=0; i<miLista.num; i++)
if (strcmp(miLista.conectados[i].ciudad, ciudad) == 0)
write (miLista.conectados[i].socket, notificacion, strlen(notificacion));
}
}
Solicitud de desconexión
else // solicitan desconexión
{
pthread_mutex_lock( &mutex );
EliminaConectado (&miLista, sock_conn);
pthread_mutex_unlock( &mutex);
fin = 1;
}