As always, let’s get straight to the point without any fluff~epoll is an efficient I/O multiplexing mechanism provided by Linux, designed to replace select and poll, addressing their performance bottlenecks when handling a large number of file descriptors. Compared to traditional select and poll, epoll has significant performance advantages when dealing with a large number of connections, especially in network servers and high-concurrency applications.1. Working Principle of epollThe basic idea of epoll is to use the event notification mechanism provided by the kernel to notify the application of changes in the state of file descriptors (such as readable, writable, exceptional, etc.), avoiding the “polling” method used in traditional I/O multiplexing mechanisms, thus reducing unnecessary checks of file descriptors.
- In epoll, the application adds file descriptors to the epoll instance, and the kernel monitors the state changes of these file descriptors.
- When the state of a file descriptor changes, the kernel places the relevant events into an event queue, and the application retrieves and processes these events by calling epoll_wait.
- This mechanism makes epoll more efficient than select and poll, especially when handling a large number of file descriptors.
2. epoll Function Prototypesepoll mainly has three functions:
- epoll_create: Creates a new epoll instance.
- epoll_ctl: Used to add, modify, or delete file descriptors in the epoll instance.
- epoll_wait: Waits for events on file descriptors and returns the events that occurred.
2.1 epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
- size: Specifies the size of the internal event queue in the kernel, usually a large number (e.g., 1024). Note that after Linux version 2.6.8, the size parameter is ignored, and epoll actually allocates memory dynamically.
Return value: Returns a file descriptor for the epoll instance; if it fails, returns -1 and sets errno.2.2 epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- epfd: The file descriptor of the epoll instance created by epoll_create.
- op: Operation type, which can be one of the following:
EPOLL_CTL_ADD: Adds the file descriptor fd to the epoll instance.
EPOLL_CTL_MOD: Modifies the events monitored for the file descriptor fd in epoll.
EPOLL_CTL_DEL: Deletes the file descriptor fd from the epoll instance.
- fd: The file descriptor to be operated on.
- event: A pointer to the epoll_event structure used to set the events of interest.
Return value: Returns 0 on success; returns -1 on failure and sets errno.2.3 epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- epfd: The file descriptor of the epoll instance created by epoll_create.
- events: A pointer to an array of epoll_event structures, where epoll_wait will return information about the ready file descriptor events.
- maxevents: Specifies the size of the events array, indicating the maximum number of events that can be returned.
- timeout: The timeout value for waiting for events (in milliseconds). If it times out, epoll_wait will return 0. If timeout is -1, it blocks until an event occurs.
Return value: Returns the number of ready events on success; returns -1 on failure.3. epoll_event Structure
struct epoll_event {
__uint32_t events; // Types of events to monitor
epoll_data_t data; // Associated data
};
- events: Indicates the types of events to monitor, which can be a combination of the following:
EPOLLIN: Indicates that the corresponding file descriptor is readable (data available, connection readable, etc.).
EPOLLOUT: Indicates that the corresponding file descriptor is writable (can write data).
EPOLLERR: Indicates that an error has occurred.
EPOLLHUP: Indicates that the file descriptor has been hung up.
EPOLLRDHUP: Indicates that the connection has been closed (TCP connection).
EPOLLET: Indicates that edge-triggered mode is used. This is a unique feature of epoll to reduce the number of system calls.
EPOLLONESHOT: Indicates that the event is triggered only once and must be registered again using epoll_ctl.
- data: Can be used to store any data associated with the file descriptor, typically used to save the file descriptor or related structures.
4. Steps to Use epoll
- Create epoll instance: Call epoll_create to create a new epoll instance.
- Add file descriptors: Use epoll_ctl to add the file descriptors to be monitored to the epoll instance and specify the types of events to monitor.
- Wait for events: Call epoll_wait to wait for events to occur.
- Process events: When events occur on certain file descriptors, epoll_wait returns the ready file descriptors, and the application can perform corresponding read and write operations.
- Modify or delete file descriptors: If necessary, use epoll_ctl to modify or delete file descriptors.
5. Example Code for epollBelow is a simple example using epoll for I/O multiplexing, demonstrating how to handle multiple client connections.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define MAX_EVENTS 10
#define PORT 8080
int main() {
int server_fd, epoll_fd;
struct epoll_event event, events[MAX_EVENTS];
// Create epoll instance
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// Create listening socket (binding, listening steps omitted)
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// Set non-blocking mode
fcntl(server_fd, F_SETFL, O_NONBLOCK);
// Add listening socket to epoll instance
event.data.fd = server_fd;
event.events = EPOLLIN | EPOLLET; // Monitor readable events and enable edge-triggered mode
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
perror("epoll_ctl: server_fd");
exit(EXIT_FAILURE);
}
while (1) {
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (n == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < n; i++) {
if (events[i].data.fd == server_fd) {
// Accept client connection
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
continue;
}
// Set non-blocking
fcntl(client_fd, F_SETFL, O_NONBLOCK);
// Add client to epoll
event.data.fd = client_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
perror("epoll_ctl: client_fd");
continue;
}
} else {
// Process client data (assuming each client sends only one line of message)
char buf[1024];
int bytes = read(events[i].data.fd, buf, sizeof(buf));
if (bytes == -1) {
if (errno != EAGAIN) {
perror("read");
close(events[i].data.fd);
}
} else if (bytes == 0) {
// Client closed connection
close(events[i].data.fd);
} else {
// Process the received data
printf("Received: %s\n", buf);
// Assume we return a response message after processing the data
const char *response = "Message received";
write(events[i].data.fd, response, strlen(response));
// If no more data to read, close connection
if (bytes == 0) {
close(events[i].data.fd);
}
}
}
}
}
close(epoll_fd);
close(server_fd);
return 0;
}
6. Edge Triggered vs Level TriggeredWhen using epoll, events can be triggered in two modes:
- Level Triggered (LT): This is the default mode, where epoll_wait returns all currently ready file descriptors, even if the data of the file descriptor has not been completely read. Level triggered mode is the most commonly used mode for most applications.
- Edge Triggered (ET): When a file descriptor changes from non-readable to readable, epoll only triggers the event once and does not trigger it again. This mode requires the application to read or write data in one go; otherwise, some events may be missed. Edge triggered mode is typically used with non-blocking I/O to improve performance.
7. Advantages of epollCompared to traditional select and poll, epoll has several significant advantages:
- Efficient event notification: The epoll kernel notifies the application promptly based on changes in file descriptors, rather than polling like select and poll.
- Ability to handle a large number of connections: epoll performs well in scenarios with a large number of file descriptors, especially when there are few events, as it does not traverse all file descriptors every time.
- Edge-triggered support: epoll provides edge-triggered mode, which can reduce unnecessary system calls and event notifications, improving overall system performance.
8. Best Practices for Using epoll
- Non-blocking I/O: When using edge-triggered mode, ensure that all file descriptors are set to non-blocking mode to prevent event loss.
- Read or write in one go: In edge-triggered mode, ensure that you read or write data in one go whenever processing events. Otherwise, events may be lost.
- Avoid excessive registration and deletion of file descriptors: In high-concurrency scenarios, try to minimize frequent operations of registering and deleting file descriptors with epoll_ctl, as this may affect performance.
In summaryepoll is a highly efficient I/O multiplexing mechanism on Linux, particularly suitable for applications handling a large number of concurrent connections. By using epoll, we can avoid the performance bottlenecks of traditional select and poll, leveraging the kernel’s event notification mechanism for efficient file descriptor monitoring and event handling.If you are developing high-concurrency network servers or handling a large number of concurrent connections, epoll is a highly recommended technology.Did you gain a lot from reading this? If it was useful to you, feel free to follow the author~ Thank you!


