Post

ft_irc - Internet Relay Chat Server

Introduction :

The Internet Relay Chat (IRC) protocol, standardized in RFC 1459 (1993), is an application layer protocol that facilitates text communication. It operates on a client-server model over TCP/IP, allowing users to connect, join channels, and exchange messages in real-time.

Project goals :

ft_irc is a 1337 project that aims to recreate an IRC server in C++98. The server must handle multiple concurrent client connections using non-blocking I/O and multiplexing operations, without relying on multi-threading or multi-processing architectures.

Walkthrough :

:one: Socket Creation and Binding :

Initialize the server by creating a socket, setting socket options, and binding it to a port.

1
2
3
4
5
6
7
8
9
10
11
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, SOMAXCONN);

:two: Configuring Non-blocking I/O :

Ensure the socket operations do not halt the execution thread.

1
fcntl(server_fd, F_SETFL, O_NONBLOCK);

:three: Multiplexing with poll or epoll :

Use an I/O multiplexing system call to monitor multiple file descriptors to see if I/O is possible on any of them.

1
2
3
4
5
6
7
8
9
10
11
12
std::vector<struct pollfd> poll_fds;
struct pollfd pfd;
pfd.fd = server_fd;
pfd.events = POLLIN;
poll_fds.push_back(pfd);

while (true) {
    int poll_count = poll(poll_fds.data(), poll_fds.size(), -1);
    if (poll_count < 0)
        break;
    // Iterate through poll_fds to accept new clients or read data
}

:four: Parsing the IRC Protocol :

When data is received from a client file descriptor, parse the raw text according to the IRC specification. Each command is delimited by \r\n.

1
Command structure: [:<prefix>] <command> <parameters> \r\n

Implement command handlers for standard IRC actions: NICK, USER, JOIN, PRIVMSG, PART, and QUIT.

:five: Channel Management :

Maintain data structures tracking connected users and active channels. When a PRIVMSG is targeted at a channel (#channel_name), iterate through the channel’s member list and execute send() to push the message payload to each corresponding file descriptor.

Questions and answers

:question: What is the difference between blocking and non-blocking I/O?

In blocking I/O, a system call like read() or accept() will suspend the execution of the thread until data is available or a connection is established. In non-blocking I/O, the system call returns immediately with an error (like EAGAIN or EWOULDBLOCK) if the operation cannot be completed instantly, allowing the program to continue executing.

:question: Why use poll instead of select?

select operates using a fixed-size bitmask (fd_set), inherently limiting the maximum file descriptor value it can monitor (typically 1024). poll utilizes an array of pollfd structures, removing the hardcoded upper limit on file descriptor values, making it more scalable for concurrent connections.

:question: What does htons do?

htons stands for “host to network short”. It converts a 16-bit integer from the host byte order (often little-endian) to network byte order (big-endian), ensuring consistent data transmission across different computer architectures.

Ressources :

  • RFC 1459 (IRC Protocol) : https://datatracker.ietf.org/doc/html/rfc1459
  • Beej’s Guide to Network Programming : https://beej.us/guide/bgnet/
  • Linux poll manual : https://man7.org/linux/man-pages/man2/poll.2.html
This post is licensed under CC BY 4.0 by the author.