0Pricing
C Academy · Lección

El bucle de eventos

Reaccione a sockets listos para lectura

El bucle de eventos es una lección gratuita de C Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C Academy incluye 4 lecciones en total.

El núcleo del servidor

El bucle de eventos es el motor que impulsa un servidor basado en epoll. Espera repetidamente a que haya descriptores listos y distribuye el trabajo entre los controladores.

Su estructura es siempre la misma: llamar a epoll_wait(), recorrer los eventos devueltos, actuar sobre cada uno y volver a empezar. Todo lo demás se apoya en este esqueleto.

for (;;) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < n; i++) handle(&events[i]);
}

Llamada a epoll_wait

epoll_wait() recibe el fd de epoll, una matriz de salida de epoll_event, su capacidad y un tiempo de espera en milisegundos.

Se bloquea hasta que al menos un descriptor está listo o transcurre el tiempo de espera, y devuelve cuántos eventos se han escrito en la matriz. Solo esas entradas son válidas.

struct epoll_event events[MAX_EVENTS];
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) { perror("epoll_wait"); }

El argumento de tiempo de espera

Un tiempo de espera de -1 bloquea indefinidamente hasta que llega un evento. El valor 0 devuelve el resultado inmediatamente, sondeando sin esperar.

Un valor positivo limita cuánto tiempo se espera, lo cual resulta útil si también necesita ejecutar tareas periódicas, como vaciar los registros o hacer caducar las conexiones inactivas.

int n = epoll_wait(epfd, events, MAX_EVENTS, 1000); /* up to 1s */
if (n == 0) run_periodic_tasks();

Dimensionamiento de la matriz de eventos

La capacidad de la matriz limita cuántos eventos listos puede informar una llamada a epoll_wait(). Si hay más eventos listos, el resto se devuelve en la siguiente llamada.

Un tamaño moderado, como 64 o 1024, es suficiente; epoll rota de forma equitativa los eventos pendientes entre las llamadas, por lo que ninguno queda bloqueado indefinidamente.

#define MAX_EVENTS 64
struct epoll_event events[MAX_EVENTS];

Distribución en el socket de escucha

Dentro del bucle, compruebe primero si el descriptor listo es su socket de escucha. Si lo es, significa que hay nuevas conexiones pendientes.

Acéptelas, configure los sockets como no bloqueantes y regístrelos en epoll. Todos los demás descriptores corresponden a clientes existentes con datos que leer o espacio disponible para escribir.

if (events[i].data.fd == listen_fd) {
    accept_new_connections(epfd, listen_fd);
} else {
    handle_client(epfd, &events[i]);
}

Aceptación de conexiones en un bucle

Un evento de disponibilidad en el socket de escucha puede indicar que hay varias conexiones pendientes. Repita el bucle sobre accept() hasta que devuelva EAGAIN.

Esto es esencial en el modo de activación por flanco y constituye una buena práctica en cualquier caso, para no dejar a los clientes esperando hasta la siguiente activación.

for (;;) {
    int c = accept(listen_fd, NULL, NULL);
    if (c == -1) { if (errno == EAGAIN) break; else break; }
    set_nonblocking(c);
    add_to_epoll(epfd, c);
}

Gestión de eventos de lectura

Cuando se produzca EPOLLIN en un cliente, lea de él. En el modo de activación por nivel, una sola llamada a recv() por activación es aceptable.

Un valor devuelto de 0 significa que el cliente cerró la conexión: libere sus recursos. Un valor negativo con EAGAIN significa que, por ahora, ha leído todos los datos disponibles.

ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r == 0) { close_conn(epfd, fd); }
else if (r > 0) { process(buf, r); }

Vaciado en el modo de activación por flanco

Con EPOLLET, debe leer hasta que recv() devuelva EAGAIN. El kernel solo le notifica la transición al estado legible, por lo que los bytes restantes se perderían hasta el siguiente cambio.

Coloque la lectura en un bucle y salga únicamente cuando se produzca EAGAIN o se alcance el final del flujo.

for (;;) {
    ssize_t r = recv(fd, buf, sizeof buf, 0);
    if (r > 0) process(buf, r);
    else if (r == 0) { close_conn(epfd, fd); break; }
    else { if (errno == EAGAIN) break; else { close_conn(epfd, fd); break; } }
}

Gestión de eventos de escritura

EPOLLOUT se produce cuando un socket puede aceptar más datos salientes. Solo lo necesita cuando tiene bytes almacenados en búfer que no se pudieron enviar por completo anteriormente.

Vacíe el búfer; cuando esté vacío, vuelva a dejar el interés en solo EPOLLIN mediante EPOLL_CTL_MOD para evitar una avalancha constante de eventos de disponibilidad para escritura.

if (events[i].events & EPOLLOUT) {
    flush_pending(fd);
    if (buffer_empty(fd)) watch_read_only(epfd, fd);
}

Gestión de esperas interrumpidas

epoll_wait() puede devolver -1 con errno == EINTR si una señal lo interrumpe. No es un error real; simplemente vuelva a intentarlo.

Los bucles robustos continúan cuando reciben EINTR y solo consideran fatales los demás códigos de error. Olvidarlo puede hacer que el servidor se bloquee la primera vez que reciba una señal.

int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) {
    if (errno == EINTR) continue;
    perror("epoll_wait"); break;
}

Un bucle, muchos clientes

Al combinarlo todo, un único subproceso recorre indefinidamente los descriptores listos: aceptar, leer, escribir, cerrar y repetir.

Como el subproceso permanece dormido en epoll_wait() cuando no hay actividad y procesa únicamente el trabajo listo cuando la hay, este diseño gestiona cómodamente decenas de miles de conexiones simultáneas.

Comprobación rápida

Compruebe cuánto ha entendido del bucle de eventos.

Resumen

El bucle de eventos llama a epoll_wait(), recorre los eventos listos y distribuye el trabajo: acepta en el socket de escucha, lee con EPOLLIN, vacía el búfer con EPOLLOUT y libera recursos cuando se produce un cierre.

Repita las operaciones de aceptación y lectura, vuelva a intentarlo con EINTR y vacíe completamente los datos con EPOLLET. A continuación, ensamblaremos un servidor de eco completo.

Preguntas frecuentes

¿La lección «El bucle de eventos» es gratis?

Sí — el texto completo de «El bucle de eventos» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C Academy, actualiza a CoddyKit PRO. El curso de C Academy incluye 4 lecciones en total.

¿Qué aprenderé en «El bucle de eventos»?

Reaccione a sockets listos para lectura Practicas C Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C Academy?

No se requiere experiencia previa. C Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «El bucle de eventos»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C Academy?

Sí. Cada lección de C Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. E/S bloqueante frente a no bloqueante
  2. Configurar epoll
  3. El bucle de eventos
  4. Un servidor de eco sencillo
← Volver a C Academy