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;
}