epoll 是什么?
- Linux 下高性能 I/O 多路复用机制
- 用来同时监听很多个 socket/文件描述符,谁就绪就处理谁
- 是 Redis、Nginx、网关、高并发服务器 的核心
为什么要用 epoll?
对比:
- select/poll:每次都要遍历所有 fd,O(n),并发高就崩
- epoll:内核主动告诉你哪些就绪,O(1),百万并发无压力
三个核心 API
1. epoll_create
创建一个 epoll 实例,返回 epfd。
2. epoll_ctl
增/删/改要监听的 fd
- EPOLL_CTL_ADD
- EPOLL_CTL_MOD
- EPOLL_CTL_DEL
3. epoll_wait
阻塞等待事件就绪,返回就绪列表,不用遍历全部。
两种触发模式(超级重要)
1. 水平触发 LT(默认)
- 只要缓冲区有数据,就一直通知
- 简单、不容易丢数据
- 新手/通用场景用它
2. 边缘触发 ET
- 只有状态变化时才通知(无→有)
- 必须一次性读完所有数据
- 性能更高,Nginx/Redis 用它
工作流程(极简版)
1. 创建 socket → bind → listen
2. 创建 epoll 实例
3. 把 listenfd 加入 epoll
4. 循环 epoll_wait
5. 就绪:
- 是 listenfd → accept 新连接,加入 epoll
- 是普通 fd → read/write 处理数据
为什么 epoll 快?
- 内核事件驱动,不轮询
- mmap 共享内存,减少拷贝
- 内部用红黑树管理 fd,增删改极快
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENTS 1024
#define PORT 8888
// 设置非阻塞
int set_nonblock(int fd) {
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);
return 0;
}
int main() {
// 1. 创建监听 socket
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
// 端口复用
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(listen_fd, 128);
set_nonblock(listen_fd);
// 2. 创建 epoll
int epfd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN; // 监听读事件
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
struct epoll_event events[MAX_EVENTS];
printf("epoll server running on port %d...\n", PORT);
while (1) {
// 3. 等待事件
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; ++i) {
int fd = events[i].data.fd;
// 新连接
if (fd == listen_fd) {
int client_fd = accept(listen_fd, NULL, NULL);
set_nonblock(client_fd);
ev.events = EPOLLIN | EPOLLET; // ET 边缘触发
ev.data.fd = client_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
}
// 客户端发来数据
else {
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf));
if (n <= 0) {
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
} else {
write(fd, buf, n); // echo 回去
}
}
}
}
close(listen_fd);
close(epfd);
return 0;
}
g++ server.cpp -o epoll_server
./epoll_server