1. 简介
epoll 的优点:
- 当检查大量的文件描述符时,epoll 的性能比 select() 和 poll() 要高。
- epoll 既支持水平触发也支持边缘触发,select() 和 poll() 只支持水平触发。
epoll 的缺点:epoll 是 Linux 特有的,不可移植。
2. 函数原型
#include <sys/epoll.h>
int epoll_create(int size);
- 创建一个新的 epoll 实例。
size
参数告诉内核应如何为内部数据结构划分初始大小,自 Linux 2.6.8 以后,size
参数被忽略不用。 - 出错时返回 -1,成功时返回 epoll 实例的文件描述符,当这个文件描述符不再需要时,应通过
close()
来关闭它。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-
修改文件描述符
epfd
所指代的 epoll 实例的兴趣列表。 -
成功时返回 0,出错时返回 -1。
-
fd
指明了要修改兴趣列表中的哪一个文件描述符的设置,它可以是管道、FIFO、套接字、POSIX 消息队列、inotify 实例、另一个 epoll 实例的文件描述符,但不能是普通文件或目录。 -
op
指明了要执行何种操作,可以为:EPOLL_CTL_ADD
:添加fd
至兴趣列表中去,感兴趣的事件由event
参数指定;兴趣列表中必须事先不存在fd
。EPOLL_CTL_MOD
:修改fd
的感兴趣事件为event
中指定的值,fd
必须已存在于兴趣列表中。EPOLL_CTL_DEL
:从兴趣列表中移除fd
,忽略event
参数;fd
必须已存在于兴趣列表中。
-
event
参数指定了感兴趣的事件:typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
epoll_event
中的events
字段指定了要监听哪些事件,data
字段用于指定当事件就绪时回传给调用进程的信息。- 常用的事件如下:
EPOLLIN
:读就绪;EPOLLOUT
:写就绪;EPOLLRDHUP
:流套接字的对端关闭或关闭了写端;EPOLLERR
:有错误发生,总是会监听此类事件,无需手动指定;EPOLLHUP
:出现挂断,总是会监听此类事件,无需手动指定;EPOLLET
:使用边缘触发;EPOLLONESHOT
:一次性检查,即完成通知后禁用检查;
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 保持阻塞,直到有事件就绪或超时。
- 出错时返回 -1,超时时返回 0,否则返回就绪的文件描述符个数。
events
是一个传入传出参数,其大小为maxevents
,调用返回时,其保存了所有已就绪的文件描述符及相应的就绪事件。timeout
用于指定超时时间,以毫秒为单位,-1 表示一直阻塞,0 表示执行一次非阻塞查询。- 可以在一个线程中调用
epoll_ctl()
,另一个线程中执行epoll_wait()
。
3. 例子
#include <iostream>
#include <string>
#include <sys/epoll.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <thread>
#include <mutex>
#include <chrono>
static uint16_t Port = 6666;
static const char* IP = "127.0.0.1";
static std::string Msg("Hello, World!");
static constexpr size_t MSG_LEN = 13;
static std::mutex ioLock;
class IOManager {
public:
IOManager(int listenFd=-1, int size=4);
~IOManager();
void run();
void add_read(int fd);
void add_write(int fd);
void del(int fd);
private:
int m_epFd;
int m_listenFd;
int m_size;
struct epoll_event* m_eventList;
};
IOManager::IOManager(int listenFd, int size): m_listenFd(listenFd), m_size(size), m_eventList(nullptr) {
m_epFd = epoll_create(m_size);
m_eventList = new struct epoll_event[m_size];
if (m_listenFd >= 0) {
add_read(m_listenFd);
}
}
IOManager::~IOManager() {
if (m_eventList) {
delete [] m_eventList;
}
if (m_epFd >= 0) {
close(m_epFd);
}
}
void IOManager::add_read(int fd) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(m_epFd, EPOLL_CTL_ADD, fd, &ev);
}
void IOManager::add_write(int fd) {
struct epoll_event ev;
ev.events = EPOLLOUT;
ev.data.fd = fd;
epoll_ctl(m_epFd, EPOLL_CTL_MOD, fd, &ev);
}
void IOManager::del(int fd) {
epoll_ctl(m_epFd, EPOLL_CTL_DEL, fd, NULL);
}
void IOManager::run() {
int nReady;
char buf[MSG_LEN];
int fd;
uint32_t events;
while (true) {
nReady = epoll_wait(m_epFd, m_eventList, m_size, -1);
if (nReady == -1) {
perror("epoll_wait()");
exit(EXIT_FAILURE);
}
for (int i = 0; i < nReady; i++) {
fd = m_eventList[i].data.fd;
events = m_eventList[i].events;
if (fd == m_listenFd) {
int connFd = accept(m_listenFd, NULL, NULL);
if (connFd == -1) {
perror("accept()");
} else {
add_read(connFd);
}
} else if (events & EPOLLIN) {
read(fd, buf, MSG_LEN);
add_write(fd);
} else if (events & EPOLLOUT) {
write(fd, Msg.data(), MSG_LEN);
del(fd);
close(fd);
}
if (events & (EPOLLERR | EPOLLRDHUP | EPOLLHUP)) {
del(fd);
close(fd);
}
}
}
}
void server() {
int listenSock = socket(AF_INET, SOCK_STREAM, 0);
int value = 1;
setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(Port);
addr.sin_family = AF_INET;
inet_pton(AF_INET, IP, &addr.sin_addr);
bind(listenSock, (const struct sockaddr*)&addr, sizeof(addr));
listen(listenSock, 4);
IOManager manager(listenSock);
manager.run();
}
void client() {
using namespace std::literals;
std::this_thread::sleep_for(1s);
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(Port);
addr.sin_family = AF_INET;
inet_pton(AF_INET, IP, &addr.sin_addr);
connect(sock, (const struct sockaddr*)&addr, sizeof(addr));
char buf[MSG_LEN+1] = {0};
ssize_t n = write(sock, Msg.c_str(), MSG_LEN);
read(sock, buf, n);
std::lock_guard<std::mutex> lock(ioLock);
std::cout << "client: " << buf << '\n';
close(sock);
}
int main() {
constexpr int N = 4;
for (int i = 0; i < N; i++)
{
std::thread t(client);
t.detach();
}
std::thread st(server);
st.join();
return 0;
}