Skip to content

mobouifr/Ft_irc

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

138 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This project has been created as part of the 42 curriculum by mobouifr and oer-refa.

ft_irc

A non-blocking IRC server written in C++98.

One thread. One poll() loop. Many clients.

Language Build 42


What is this?

ft_irc is a fully functional IRC server that handles multiple simultaneous clients over TCP — without threads, without fork(), and without any blocking I/O. Every connection, every message, every command flows through a single poll()-driven event loop.

The technically interesting part is not just protocol compliance. It's systems behavior: how a single process stays responsive to dozens of clients at once by reacting only to file descriptors the kernel has marked ready. No busy-waiting. No threads to synchronize. Just one loop, one table of descriptors, and careful I/O discipline.

The server is designed to work with real IRC clients. HexChat was the reference client during development and evaluation, with telnet and nc used for fragment and edge-case testing.

No threads. No fork(). No blocking. Just poll() and the IRC protocol.


How it works

The poll() event loop

poll() is the scheduler of the entire server. Instead of blocking on one socket at a time, it asks the kernel which file descriptors are currently ready, then handles only those. This is why a single thread can accept new connections, read commands, and send replies — all concurrently.

struct pollfd {
    int   fd;       // the file descriptor to watch
    short events;   // what you want to know about
    short revents;  // what actually happened (written by the kernel)
};
pollfd struct and bitmask reference

The pollfd struct and bitmask reference used in the server's event loop


What poll() tells the kernel

What poll() actually tells the kernel — in plain terms


When poll() returns, the server inspects each entry in _pollTable[i].revents. POLLIN triggers a recv(). POLLOUT triggers a send(). Everything else is an error or a disconnect.

POLLIN and POLLOUT behavior

POLLIN and POLLOUT: when the kernel signals readiness and what to do next


Data journey: from client to server

End-to-end message path

End-to-end path of a message: from IRC client keypress to server handler


  1. The user types a command in an IRC client
  2. The client calls send() — bytes enter the client-side TCP stack
  3. The kernel segments and transmits them via the NIC
  4. The server kernel reassembles them into an ordered byte stream
  5. The stream lands in the socket receive buffer — poll() returns with POLLIN
  6. The event loop calls recv() and appends bytes to the per-client input buffer
  7. The parser extracts complete \r\n-terminated lines, holding fragments until the next cycle
  8. Each complete line is dispatched to its command handler, which queues a reply for the next POLLOUT cycle
Kernel-side packet handling

How the kernel processes incoming packets before poll() and recv() are involved


Command dispatch

Incoming bytes are not interpreted immediately. They accumulate in a per-client buffer until a full \r\n-terminated line is present — which prevents corruption when packets arrive fragmented across multiple recv() calls.

Each complete line is tokenized and routed to its handler: PASS, NICK, USER, JOIN, PRIVMSG, NOTICE, MODE, KICK, INVITE, TOPIC, PART, CAP, or QUIT. The handler validates connection state and arguments, builds the appropriate numeric or textual response, and queues outbound data for the next writable cycle.


Architecture overview

Server architecture diagram

Multiple clients, one listening socket, one poll table

The listening socket is created once via socket(), bind(), and listen(), then monitored permanently by poll(). Each accept() produces a new client file descriptor added to _pollTable. One communication channel per client. One event loop for all of them.

View the full interactive Excalidraw board →


Features

Feature Notes
Multi-client via poll() Single-threaded, fully non-blocking
Password authentication Required on connection via PASS
Nick & user registration Full NICK / USER flow
Channel join & broadcast JOIN, PART, channel-wide PRIVMSG
Private messaging User-to-user PRIVMSG and NOTICE
Operator privilege system Two roles: operator and regular user
KICK Eject a client from a channel
INVITE Invite a client into a channel
TOPIC View or set the channel topic
MODE i Invite-only channel
MODE t Operator-only topic changes
MODE k Channel password
MODE o Grant / revoke operator
MODE l User limit on channel
Partial packet buffering Fragments held until \r\n completes
Signal handling Clean shutdown on SIGINT and SIGQUIT

Technical constraints

  • Strict C++98 — no C++11 or later
  • No external libraries, no Boost
  • No fork(), no threads
  • All I/O is non-blocking via O_NONBLOCK
  • A single poll() handles everything: listen, accept, read, write
  • On macOS: only fcntl(fd, F_SETFL, O_NONBLOCK) is permitted

Reading or writing any file descriptor outside the poll()-driven flow is an automatic 0.


Build and run

make
./ircserv <port> <password>
# Example
./ircserv 6667 mysecretpassword

Connect with HexChat: Add a custom network pointing to 127.0.0.1/6667 with the password mysecretpassword.

Connect with telnet:

telnet 127.0.0.1 6667
PASS mysecretpassword
NICK mynick
USER myuser 0 * :my real name

Test partial packet handling with nc:

nc -C 127.0.0.1 6667

Type com, press Ctrl+D, type mand, press Ctrl+D, type \r\n. The server must reassemble fragments into one complete command before processing.

Makefile rules

Rule Effect
make Build ircserv
make clean Remove object files
make fclean Remove objects and binary
make re Full clean rebuild

Project structure

ft_irc/
│
├── Makefile
├── Includes/
│   ├── Server.hpp               ← server class declarations
│   ├── Client.hpp               ← client class declarations
│   ├── Channel.hpp              ← channel class declarations
│   ├── CommandHandler.hpp       ← command dispatch interface
│   ├── Bot.hpp                  ← bot client declarations
│   ├── NumericReplies.hpp       ← IRC numeric reply constants
│   └── Headers.hpp              ← shared includes and definitions
│
├── Src/
│   ├── main.cpp
│   ├── Server/
│   │   ├── Server.cpp           ← server class implementation
│   │   ├── ServerCore.cpp       ← core runtime logic
│   │   ├── ServerInit.cpp       ← socket setup and initialization
│   │   ├── ServerPoll.cpp       ← poll() loop and event dispatch
│   │   ├── ServerIO.cpp         ← socket I/O operations
│   │   ├── ServerParseLine.cpp  ← input buffering and line parsing
│   │   ├── ServerHelpers.cpp    ← shared server utilities
│   │   └── ServerSig.cpp        ← SIGINT / SIGQUIT handling
│   ├── Client/
│   │   ├── Client.cpp
│   │   ├── ClientHelpers.cpp
│   │   └── Bot.cpp
│   ├── Channel/
│   │   ├── Channel.cpp
│   │   └── ChannelHelpers.cpp
│   └── Commands/
│       ├── CommandHandler.cpp   ← routing and dispatch
│       ├── CommandHelpers.cpp   ← shared command logic
│       ├── Pass.cpp · Nick.cpp · User.cpp · Cap.cpp
│       ├── Join.cpp · Part.cpp · Quit.cpp
│       ├── Privmsg.cpp · Notice.cpp
│       ├── Kick.cpp · Invite.cpp · Topic.cpp · Mode.cpp
│       └── (one file per command)
│
└── docs/assets/                 ← diagram screenshots used in this README

Resources

About

A robust IRC server in C++98 with multi-client support, channel management, and core IRC commands over TCP sockets.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • C++ 88.2%
  • Makefile 9.7%
  • Python 2.1%