Building An Irc Server In C++
I was looking forward to do this project since i learned about it, Luis and i had to create our own IRC server in C++, fully compatible with an official client.
Server Structure
The IRC server is built using standard socket programming in C++, following an event-driven architecture using epoll for high-performance networking. Below are the core components of the server:
- Server Class: Manages socket initialization, client connections and their requests
- Client Class: Represents individual clients connected to the server.
- Channel Class: Manages channels and user interactions within them.
- Command Classes : Implements each IRC command.
Handling sockets
The server starts by:
- Creating a non-blocking socket using
socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - Binding the socket to a specified port.
- Setting socket options (
SO_REUSEADDRfor quick reuse of ports). - Listening for incoming connections (
listen(server_socket_, 100);). - Creating an
epollinstance (epoll_create1(0);). - Adding the server socket to the
epollevent loop.
The First Challenge: Choosing the Right Multiplexing Method
We had 4 ways to deal with the connections:
select– Why?poll– What most people were using.epoll– We didn’t know about thiskqueue– The BSD/macOS counterpart toepoll, 42 used macOS for several years so we had that choice.
Why poll Wasn’t the Answer
At first, poll seemed like a reasonable choice—it was straightforward, it worked, and most other projects we looked at were using it. But as we started implementing it, a couple of issues popped up:
- Detecting disconnections was unreliable : Clients would disconnect but the poll flags wouldn’t be triggered, the workaround was really, really ugly.
- Performance concerns : Iterating trough the entire list of clients each time we had to perform an action was a bit ugly, and handling adding or removing clients on the go was doable, but not ideal
Why epoll Was the answer
-
No Need for
pollfdStructures – Unlikepoll, where we need to maintain and update apollfdstructure for every socket,epolleliminates this requirement. Instead of keeping an array of all connections and scanning through them,epolldirectly provides the active file descriptors, reducing memory overhead and complexity. -
Speed Boost – Since
epolldoesn’t require iterating through an entire list of sockets to find which ones are active, it significantly reduces CPU usage. This means handling a large number of connections is much faster compared topoll, which suffers from O(n) complexity per call. -
Smarter Event Handling – With
poll, every time a new connection comes in or an existing one changes state, we have to update the entirepollfdlist.epoll, on the other hand, allows us to dynamically add, modify, or remove events without rebuilding a full structure, making it far more efficient for high-performance servers.
At the end, we had this:
void Server::HandleEvents() {
epoll_event ev_fd[MAX_EVENTS];
const int num_events = epoll_wait(epfd_, ev_fd, MAX_EVENTS, -1);
if (num_events == -1)
throw std::runtime_error("Error: Error while waiting on epoll.");
if (num_events > 0) {
for (int i = 0; i < num_events; ++i) {
const int fd = ev_fd[i].data.fd;
const uint32_t events = ev_fd[i].events;
if (events & (EPOLLRDHUP | EPOLLHUP))
ClientDisconnect(fd);
else if (fd == server_socket_ && (events & EPOLLIN))
ClientConnect();
else if (events & EPOLLIN)
ClientMessage(fd);
}
}
}
Each client connection is added to the epoll event queue using:
void Server::AddEpoll(const int fd, const uint32_t events) const {
epoll_event ev = {};
ev.events = events;
ev.data.fd = fd;
if (epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev) == -1)
throw std::runtime_error("Error: failed to add file descriptor to epoll.");
}
Understanding IRC Command Behavior: RTF… RFC?
One of the biggest challenges in building the IRC server—right after setting up efficient socket handling—was figuring out exactly how each command should behave. Unlike a simple chat server where you can make up your own rules, IRC has a well-defined set of commands that clients expect to work in a specific way.
Conclusion
This was fun! Big thanks to Luis for being an awesome teammate and the laughs we had building this, you can check the code here