C Academy · Lesson

A Simple Echo Server

Serve many clients at once.

Lesson 4 of 413 steps

A Simple Echo Server is a free C Academy lesson on CoddyKit. This is lesson 4 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, and your progress syncs across the web and the CoddyKit app. The C Academy course includes 4 lessons in total.

What We Are Building

An echo server accepts TCP connections and sends back whatever bytes a client sends. It is the "hello world" of network programming.

We will build it on epoll so a single thread serves many clients at once. The pieces are: a listening socket, the epoll loop, and per-client read-then-write handling.

Creating the Listening Socket

Start with socket() for an IPv4 TCP endpoint. The triple is AF_INET, SOCK_STREAM, and protocol 0.

This returns a file descriptor that we will bind, mark non-blocking, and eventually register with epoll. Check for -1 on every system call.

int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) { perror("socket"); exit(1); }

Reusing the Address

Before binding, set SO_REUSEADDR with setsockopt(). This lets the server restart and rebind to the same port without waiting out the TIME_WAIT state.

Without it, a quick restart often fails with EADDRINUSE, which is a frustrating surprise during development.

int yes = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);

Binding to a Port

Fill a struct sockaddr_in with the family, port, and address. Use htons() for the port and INADDR_ANY to listen on all interfaces.

Then call bind() to attach the socket to that address. htons converts host byte order to network byte order, which is required.

struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);
bind(listen_fd, (struct sockaddr*)&addr, sizeof addr);

Listening and Going Non-Blocking

Call listen() to mark the socket passive and set a backlog for pending connections. SOMAXCONN requests the system maximum.

Then switch the socket to non-blocking so accept() never stalls the event loop. Now it is ready to register with epoll.

listen(listen_fd, SOMAXCONN);
set_nonblocking(listen_fd);

Registering the Listener

Create the epoll instance and add the listening socket with EPOLLIN. A readable event here means a new client is waiting to be accepted.

We store the fd in data.fd so we can recognize the listener inside the loop.

int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = listen_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);

Accepting a Client

When the listener is readable, loop accept() until EAGAIN. Each new client socket is set non-blocking and registered for EPOLLIN.

From here on, epoll will tell us whenever that client sends data we can echo back.

int c = accept(listen_fd, NULL, NULL);
if (c != -1) {
    set_nonblocking(c);
    struct epoll_event cev = { .events = EPOLLIN, .data.fd = c };
    epoll_ctl(epfd, EPOLL_CTL_ADD, c, &cev);
}

The Echo Logic

For a ready client, recv() into a buffer. If you get bytes, send the same bytes straight back with send().

This simple read-then-write is the echo. In production you would buffer unsent bytes, but for a demo we assume the small reply fits the send buffer.

char buf[4096];
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r > 0) {
    send(fd, buf, r, 0);
}

Detecting Disconnect

A recv() return of 0 means the client performed an orderly shutdown. A negative return with anything other than EAGAIN is a real error.

In both cases, remove the descriptor from epoll and close() it to free resources and stop further events.

if (r == 0 || (r < 0 && errno != EAGAIN)) {
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
}

The Full Loop

Combine the parts: wait for events, branch on whether the descriptor is the listener or a client, and act accordingly.

This compact loop is a fully functional concurrent echo server. One thread, many clients, no per-connection threads.

for (;;) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < n; i++) {
        if (events[i].data.fd == listen_fd) accept_clients();
        else echo_or_close(events[i].data.fd);
    }
}

Testing and Next Steps

Compile on Linux and connect with nc localhost 8080; whatever you type comes back. Open several terminals to prove concurrency.

To harden it: buffer partial sends, handle EPOLLOUT, switch to EPOLLET with full draining, and handle EPOLLRDHUP for half-closes.

Quick Check

Test your understanding of the echo server flow.

Recap

You built an epoll echo server: create, set SO_REUSEADDR, bind, listen, and go non-blocking. Register the listener, accept clients in a loop, and echo with recv()/send().

Handle a 0 return as disconnect and close cleanly. From this skeleton you can grow a high-performance, single-threaded network server.

Free to start

Learn C with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
39
Lessons
144

Frequently Asked Questions

Is the “A Simple Echo Server” lesson free?

Yes — the full text of “A Simple Echo Server” is free to read here on the web. 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. The C Academy course includes 4 lessons in total.

What will I learn in “A Simple Echo Server”?

Serve many clients at once. 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, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.

How long does the “A Simple Echo Server” 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