This is an IRC server, serving as an introduction to socket programming in C and C++. It is compatible with most IRC clients but was specifically designed to integrate with HexChat. The server complies with standard IRC protocols described in IRC v3.
_fd = socket(AF_INET, SOCK_STREAM, 0)-
IPv4, TCP, IPPROTO_TCP
We use
AF_INETas the first argument to specify that we want to create a socket that uses the IPv4 protocol for communication over the internet. Here's a breakdown of why each argument is used:
Domain (AF_INET):
AF_INETstands for "Address Family: Internet".- It specifies that the socket will use the IPv4 protocol, which is the most common protocol for internet communication.
- This tells the system that we want to create a socket that can communicate over the internet using IPv4 addresses.
Type (SOCK_STREAM):
SOCK_STREAMspecifies that the socket will use a reliable, connection-oriented communication method.- This means we want to use TCP (Transmission Control Protocol), which ensures that data is delivered in the same order it was sent and without errors.
Protocol (0):
0means we want to use the default protocol for the specified type.- For
SOCK_STREAM, the default protocol is TCP.
- AF_INET: Use the IPv4 protocol for internet communication.
- SOCK_STREAM: Use a reliable, connection-oriented communication method (TCP).
- 0: Use the default protocol for
SOCK_STREAM, which is TCP.
_fd = socket(AF_INET, SOCK_STREAM, 0)
| | |
| | |
| | +--- Protocol: Default protocol (0 for TCP)
| +---------- Type: Communication style (SOCK_STREAM for TCP)
+------------------ Domain: Network type (AF_INET for IPv4)
So, by using AF_INET as the first argument, we are specifying that we want to create a socket that can communicate over the internet using IPv4 addresses.
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPv4
addr.sin_port = htons(port); // Port number
addr.sin_addr.s_addr = INADDR_ANY; // Accept connections from any IP- bind(): Associates socket with specific address and port
- listen(): Marks socket as passive to accept incoming connections
- fcntl(): Sets socket to non-blocking mode
- Socket operations return immediately
- If operation can't be completed, returns error (usually EAGAIN/EWOULDBLOCK)
- Program can continue executing other tasks
- Typically used with poll() or select() to check when socket is ready
- Server needs to handle multiple clients simultaneously
- Can't afford to wait/block on any single client
- poll() is used to monitor multiple sockets for activity
- More efficient use of resources than creating thread per client
- setsockopt(): Sets socket options (here enables address reuse, allowing multiple clients with the same network address)
A passive socket is used by a server to listen for incoming connection requests from clients. It passively waits for connections.
Application Kernel
|--- socket() ---------->|
| |
|--- bind() ------------>|
| |--- associate socket with address/port
|<--- bind complete -----|
| |
|--- listen() ---------->|
| |--- mark socket as passive
|<--- ready to accept ---|
| |
|--- accept() ---------->|
| |--- wait for connection request
|<--- connection request-|
| |--- establish connection
|<--- new socket --------|
| |
|--- handle client ------|
An active socket is used by a client to initiate a connection to a server. It actively seeks to establish a connection.
Application Kernel
|--- socket() ---------->|
| |
|--- connect() --------->|
| |--- establish connection
|<--- connection ready --|
| |
|--- send/receive data -->|
| |--- handle data transfer
|<--- data received -----|
In blocking I/O, the application waits for the kernel to copy the data before continuing. In non-blocking I/O, the application can continue executing other tasks if the data is not immediately available
Blcoking I/O
Application Kernel
| |
|--- read data --------->|
| |--- wait for data
| |--- copy data
|<--- return data -------|
| |
Non-Blocking I/O
Application Kernel
| |
|--- read data --------->|
| |--- check if data is available
| |--- if no data, return immediately with error (EAGAIN/EWOULDBLOCK)
|<--- return error ------|
| |
|--- continue other tasks|
| |
|--- poll/select --------|
| |--- wait for data to be ready
| |--- data ready
|<--- data ready --------|
| |
|--- read data again --->|
| |--- copy data
|<--- return data -------|
int new_fd = accept(_fd, NULL, NULL);
- sockfd: The listening socket file descriptor (passive socket)
- addr: Pointer to a sockaddr structure where client address info will be stored
- addrlen: Pointer to length of the addr structure
int new_fd = accept(_fd, NULL, NULL);
if (fcntl(new_fd, F_SETFL, O_NONBLOCK) == -1)- IRC protocol identifies users by nickname/username, not IP address, there is no need to save the client's network address unless we are planning on:
- Access control based on IP
- Logging/analytics
- Geographic features
- Security features like banning IPs
The poll() function is used for monitoring multiple file descriptors to see if I/O is possible. Here's how it works:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; // file descriptor
short events; // events to look for
short revents; // events that occurred
};- POLLIN: Data available to read
- POLLOUT: Writing now possible
- POLLERR: Error condition
- POLLHUP: Hangup (client disconnected)
- POLLIN = 0x0001 // 0000 0000 0000 0001
- POLLOUT = 0x0004 // 0000 0000 0000 0100
- POLLERR = 0x0008 // 0000 0000 0000 1000
- Example: both read and error
revents = POLLIN | POLLERR // 0000 0000 0000 1001
- Bitwise AND (&) efficiently checks for specific events:
if (it->revents & POLLIN)
- This is more efficient than:
- Using multiple variables for each event type
- Using regular boolean operations
- Checking each event separately
- The bitmask approach allows for compact storage and fast checking of multiple event flags in a single integer.