0Pricing
C Academy · Lesson

The Event Loop

React to readable sockets.

The Event Loop is a free C Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Heart of the Server

The event loop is the engine that drives an epoll server. It repeatedly waits for ready descriptors and dispatches work to handlers.

Its shape is always the same: call epoll_wait(), iterate the returned events, act on each, then loop again. Everything else hangs off this skeleton.

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

Calling epoll_wait

epoll_wait() takes the epoll fd, an output array of epoll_event, its capacity, and a timeout in milliseconds.

It blocks until at least one descriptor is ready (or the timeout elapses) and returns how many events were filled into your array. Only those entries are valid.

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

The Timeout Argument

A timeout of -1 blocks indefinitely until an event arrives. A value of 0 returns immediately, polling without sleeping.

A positive value caps how long to wait, which is handy if you also need to run periodic tasks like flushing logs or expiring idle connections.

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

Sizing the Event Array

The array capacity caps how many ready events one epoll_wait() call can report. If more are ready, the rest are returned on the next call.

A modest size like 64 or 1024 is fine; epoll fairly rotates through pending events across calls, so nothing starves.

#define MAX_EVENTS 64
struct epoll_event events[MAX_EVENTS];

Dispatching on the Listening Socket

Inside the loop, first check whether the ready descriptor is your listening socket. If so, it means new connections are pending.

You accept them, set them non-blocking, and register them with epoll. All other descriptors are existing clients with data to read or space to write.

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

Accepting Connections in a Loop

One readiness event on the listening socket may mean several pending connections. Loop on accept() until it returns EAGAIN.

This is essential under edge-triggered mode and good practice everywhere, so you do not leave clients waiting until the next wakeup.

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

Handling Readable Events

When EPOLLIN fires on a client, read from it. Under level-triggered mode a single recv() per wakeup is acceptable.

A return of 0 means the client closed the connection: clean it up. A negative return with EAGAIN means you have drained all available data for now.

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

Draining Under Edge-Triggered

With EPOLLET you must read until recv() returns EAGAIN. The kernel notifies you only on the transition to readable, so leftover bytes would be lost until the next change.

Wrap the read in a loop and break out only on EAGAIN or end of stream.

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

Handling Writable Events

EPOLLOUT fires when a socket can accept more outbound data. You only want this when you have buffered bytes that did not fully send earlier.

Flush your buffer; once it is empty, switch the interest back to EPOLLIN only with EPOLL_CTL_MOD to avoid a constant write-ready storm.

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

Handling Interrupted Waits

epoll_wait() can return -1 with errno == EINTR if a signal interrupts it. This is not a real error; just retry.

Robust loops continue on EINTR and only treat other error codes as fatal. Forgetting this can crash a server the first time it receives a signal.

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

One Loop, Many Clients

Put it together and a single thread cycles through ready descriptors forever: accept, read, write, close, repeat.

Because the thread sleeps in epoll_wait() when idle and processes only ready work when busy, this design comfortably handles tens of thousands of concurrent connections.

Quick Check

Test your understanding of the event loop.

Recap

The event loop calls epoll_wait(), iterates the ready events, and dispatches: accept on the listening socket, read on EPOLLIN, flush on EPOLLOUT, and clean up on hangup.

Loop your accepts and reads, retry on EINTR, and drain fully under EPOLLET. Next we assemble a complete echo server.

Frequently asked questions

Is the “The Event Loop” lesson free?

Yes — the full text of “The Event Loop” is free to read here on the web, and the C Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C Academy course, upgrade to CoddyKit PRO.

What will I learn in “The Event Loop”?

React to readable sockets. You practise C Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C Academy?

No prior experience is required. C Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The Event Loop” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C Academy lesson?

Yes. Every C Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Blocking vs Non-Blocking I/O
  2. Setting Up epoll
  3. The Event Loop
  4. A Simple Echo Server
← Back to C Academy