Vistas: 460
Tiempo de lectura:4 Minutos, 32 Segundos

Creando el Servidor

El servidor en este proyecto, funciona como broker de mensajes, repartiendo los mensajes entre todos los clientes menos al emisor original. Es el encargado de decidir el puerto al cual se conectarán los clientes.

Funciones:

Este código del servidor ya es más complejo, ya que tiene que lidiar con todos los clientes y reenviar los mensajes de los remitentes. Consta de las siguientes funciones, algunas se explican por sí solas, pero vamos a intentar darles una idea de para qué sirve cada una:

void print_client_addr(struct sockaddr_in addr);
void queue_add(client_t *cl);
void queue_remove(int uid);
void send_message(char *s, int uid);
void *handle_client(void *arg);
int main(int argc, char **argv)

Función Main

Al igual que el código del cliente, desde la función main obtenemos el puerto al cual se va a configurar el servidor, la dirección ip, que seria la direccion local de la máquina.

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Uso: %s \n", argv[0]);
        return EXIT_FAILURE;
    }
 
    char *ip = "127.0.0.1"; //direccion ip del servidor
    int port = atoi(argv[1]); //convierte string a int

Nuevamente, estas son configuraciones habituales para usar sockets, no debería haber mucho problema con esto.

/* Configuracion del Socket  */
   listenfd = socket(AF_INET, SOCK_STREAM, 0);
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = inet_addr(ip);
   serv_addr.sin_port = htons(port);
 
   if (setsockopt(listenfd, SOL_SOCKET, (SO_REUSEPORT | SO_REUSEADDR),
       (char *)&option, sizeof(option)) < 0)
   {
       DieWithError("ERROR: setsockopt falló");
   }
 
   /* Bind */
   if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
   {
       DieWithError("ERROR: El enlace de socket falló");
   }
 
   /* Listen */
   if (listen(listenfd, 10) < 0)
   {
       DieWithError("ERROR: Fallo al abrir el socket");
   }

La siguiente parte del main, es un while infinito, que a través de la función “accept” está escuchando constantemente en el puerto, esperando siempre a nuevos clientes.

while (1)
   {
       socklen_t clilen = sizeof(cli_addr);
       connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &clilen);

Una vez conecta con el cliente, comprueba si ya se llenó la sala y es aquí es donde decimos cuál será el límite de usuarios con la variable “ MAX_CLIENTS”. Es una variable global que definimos al principio del código y con esta definimos el límite de la sala. Esta vez son 100, pero podrían ser menos incluso más de 100. Si esta parte del codigo encuentra que ya se llegó a la máxima capacidad la sala, simplemente desconecta el cliente y continua.

       /* Comprueba el maximo de clientes */
       if ((cli_count + 1) == MAX_CLIENTS)
       {
           printf("Max clients reached. Rejected: ");
           print_client_addr(cli_addr);
           printf(":%d\n", cli_addr.sin_port);
           close(connfd);
           continue;
       }

Luego de comprobar la conexión del cliente, se configura la estructura que definimos antes, guardando los datos importantes, como el socket, la dirección ip y el id del cliente, que en este caso simplemente es una variable incremental. Una vez definido esto, se agrega el cliente a la cola, se pasan por parámetros a un nuevo hilo dentro del servidor. Este hilo servirá para manejar los mensajes entrantes del nuevo cliente.

           /* Configuracion del cliente aceptado */
       client_t *cli = (client_t *)malloc(sizeof(client_t));
       cli->address = cli_addr;
       cli->sock = connfd;
       cli->uid = uid++;
 
       /* Agrega el cliente a la cola y crea el hilo del cliente */
       queue_add(cli);
       pthread_create(&tid, NULL, &handle_client, (void *)cli);
 
       /* Reduce uso del CPU */
       sleep(1);
   }

Función handle_client

Esta es una función muy importante, ya que manejará los mensajes entrantes de todos los clientes nuevos en un hilo de proceso a parte. Esta parte del código se encarga de recibir esos mensajes y enviarlos a los demás usuarios. Esta función también es la encargada de detectar cuando el cliente se desconecte. Cuando esto sucede, el ciclo se corta a la vez que se elimina el cliente de la cola y cierra el hilo de proceso.

while (1)
{
       if (leave_flag)
       {
           break;
       }
       memset(buff_out, 0, BUFFER_SZ); //resetea el buffer de mensajes
       int receive = recv(cli->sock, buff_out, BUFFER_SZ, 0);
       if (receive > 0)
       {
           if (strlen(buff_out) > 0)
           {
               send_message(buff_out, cli->uid);
               printf("%s\n", buff_out);
           }
       }
       else if (receive == 0)
       {
           cli_count--;
           sprintf(buff_out, "%s se ha ido - %d conectados\n", cli->name, cli_count);
           printf("%s", buff_out);
           send_message(buff_out, cli->uid);
           leave_flag = 1;
       }
       else
       {
           printf("ERROR: -1\n");
           leave_flag = 1;
       }
}

Funciones restantes

queue_add(client_t *cl) (agregar a la cola) Esta función se encarga de añadir a cada cliente nuevo a la cola de clientes, en un ciclo for comprueba un espacio libre desde el elemento cero.

queue_remove(int uid) (eliminar de la cola) Esta función se encarga de eliminar a cada cliente que se desconecte, de la cola de clientes, en un ciclo for comprueba el uid del cliente desde el elemento cero para eliminarlo.

send_message(char *s, int uid) (enviar mensaje) Esta función envía una copia de los mensajes entrantes a todos los clientes excepto al remitente, esto se hace comprobando en un ciclo for cada espacio válido de la cola evitando al uid del remitente.

A continuación en la parte final del tutorial podrán ver el resultado final del proyecto y su funcionamiento.