Implementación de servidores en C++ para Linux usando el modelo poll

El mecanismo poll pertenece a la familia de I/O multiplexado en Linux. Permite a un proceso monitorizar múltiples descriptores de archivo simultáneamente para detectar cuáles están listos para operaciones de lectura, escritura o manejo de errores. Su comportamiento es análogo al de select, pero emplea una estructura de datos diferente y elimina algunos límites prácticos.

Fundaemntos de la llamada al sistema poll

La función poll está definida en <poll.h>. Su prototipo es:

#include <poll.h>
int poll(struct pollfd *ufds, nfds_t nfds, int timeout);

El parámetro timeout controla el comportameinto de bloqueo: un valor de -1 indica bloqueo indefinido hasta que ocurra un evento; 0 hace que la llamada retorne inmediatamente; un valor positivo especifica un límite en milisegundos. La función devuelve el número total de descriptores con eventos pendientes, o -1 en caso de error.

Estructura pollfd

Cada descriptor monitorizado se representa mediante una instancia de struct pollfd:

struct pollfd {
    int   fd;         /* Descriptor de archivo */
    short events;     /* Eventos a monitorear (bitmask) */
    short revents;    /* Eventos ocurridos (llenado por el kernel) */
};

Los miembros events y revents utilizan máscaras de bits. Los eventos comunes incluyen:

  • POLLIN: Datos disponibles para lectura.
  • POLLOUT: Escritura posible sin bloqueo.
  • POLLERR: Condición de error en el descriptor.
  • POLLHUP: Conexión cerrada o tubería rota.
  • POLLRDHUP (requiere _GNU_SOURCE): Cierre del extremo remoto de un socket de flujo.

Implementación de un servidor echo con poll

A continuación se muestra un ejemplo de servidor que acepta conexiones TCP y devuelve los datos recibidos (eco). El código gestiona dinámicamente el conjunto de descriptores monitorizados.

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <poll.h>

#define MAX_EVENTS 128

int crear_socket_servidor(const char* ip, int puerto) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    sockaddr_in direccion{};
    direccion.sin_family = AF_INET;
    direccion.sin_port = htons(puerto);
    inet_pton(AF_INET, ip, &direccion.sin_addr);

    if (bind(sockfd, reinterpret_cast<sockaddr*>(&direccion), sizeof(direccion)) < 0) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, SOMAXCONN) < 0) {
        perror("listen");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Uso: " << argv[0] << " <IP> <PUERTO>\n";
        return 1;
    }

    int socket_escucha = crear_socket_servidor(argv[1], std::stoi(argv[2]));

    std::vector<pollfd> monitores;
    monitores.push_back({socket_escucha, POLLIN, 0});

    while (true) {
        int listos = poll(monitores.data(), monitores.size(), -1);
        if (listos < 0) {
            if (errno == EINTR) continue;
            perror("poll");
            break;
        }

        size_t tamaño_actual = monitores.size();
        for (size_t i = 0; i < tamaño_actual && listos > 0; ++i) {
            if (monitores[i].revents == 0) continue;
            --listos;

            if (monitores[i].fd == socket_escucha) {
                sockaddr_in cliente_addr{};
                socklen_t cliente_len = sizeof(cliente_addr);
                int nuevo_fd = accept(socket_escucha,
                                     reinterpret_cast<sockaddr*>(&cliente_addr),
                                     &cliente_len);
                if (nuevo_fd < 0) {
                    perror("accept");
                    continue;
                }
                std::cout << "Nueva conexión desde: "
                          << inet_ntoa(cliente_addr.sin_addr) << "\n";
                monitores.push_back({nuevo_fd, POLLIN, 0});
            } else {
                if (monitores[i].revents & (POLLERR | POLLHUP)) {
                    close(monitores[i].fd);
                    monitores.erase(monitores.begin() + i);
                    --i;
                    --tamaño_actual;
                    continue;
                }

                if (monitores[i].revents & POLLIN) {
                    char buffer[4096];
                    ssize_t bytes_recibidos = recv(monitores[i].fd, buffer, sizeof(buffer), 0);
                    if (bytes_recibidos <= 0) {
                        close(monitores[i].fd);
                        monitores.erase(monitores.begin() + i);
                        --i;
                        --tamaño_actual;
                        if (bytes_recibidos == 0) {
                            std::cout << "Conexión cerrada por cliente.\n";
                        } else {
                            perror("recv");
                        }
                    } else {
                        send(monitores[i].fd, buffer, bytes_recibidos, MSG_NOSIGNAL);
                    }
                }
            }
        }
    }

    for (auto& monitor : monitores) {
        close(monitor.fd);
    }
    return 0;
}

Etiquetas: C++ linux programación de red poll sockets

Publicado el 6-26 20:38