A Simple Echo Server
Serve many clients at once.
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.
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
- Blocking vs Non-Blocking I/O
- Setting Up epoll
- The Event Loop
- A Simple Echo Server