不负责任地放上中文版Epoll维基,科学上网。
原理什么的不多讲,我也不是很熟,这里就简单写一些实际应用要怎么写,当然也是很简单的demo。
结构体介绍
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 */
};
API介绍
int epoll_create(int size);
// 这个函数用来创建epoll
// 参数size是指这个epoll最多能同时监听多少个文件描述符的事件
// 函数返回值是epoll的文件描述符,linux下面文件描述符简直无处不在
int efd = epoll_create(10);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 这个函数用来管理事件的监听
// 参数epfd就是用epoll_create函数获得的文件描述符
// 参数op是一个用位数来表示配置的变量,表示当前操作是增加事件还是删除事件
// 参数event保存的是具体的事件配置
int fd = socket(FA_INET, STREAM, 0);
...
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLRDHUP;
epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event);
- op参数
- EPOLL_CTL_ADD 将目标文件描述符fd已经fd对应的事件,通过efd注册到对应的epoll上
- EPOLL_CTL_MOD 修改事件的具体事件通过文件描述符
- EPOLL_CTL_DEL 将fd对应的事件从epoll中删除
- struct epoll_event 事件及结构体的书写
- 一个epoll_event可以设置监听多种事件类型
- 最重要的是两件事,一个是设置对应的要监听的文件描述符,一个是设置监听事件类型,两者都是直接赋值的方式。
- 常用事件类型:
- EPOLLIN 可读事件(对等方套接字合法关闭也是可读事件)
- EPOLLOUT 写事件
- EPOLLRDHUP 对等方套接字关闭连接事件
- EPOLLPRI 有紧急需要读的事件
- EPOLLERR 文件描述符发生异常条件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// 这个函数用来获取当前的事件处于待处理状态的文件描述符
// epfd是epoll描述符
// events是事件列表,用来获取待处理的事件,
// maxevents表示最多能取多少个struct epoll_event
// timeout表示超时时间,如果一直没有事件响应,那么到了超时时间,函数会阻塞状态返回回来,-1表示没有超时时间,一直阻塞等待事件的到达
struct epoll_event events[10];
int ret = epoll_wait(efd, events, 10, -1);
for (int i = 0; i < ret; i++) {
char buf[1024] = { 0 };
int res = read(events[i].data.fd, buf, sizeof buf);
if (res == 0) {
epoll_ctl(efd, EPOLl_CTL_DEL, &events[i]); // 从epoll中删除文件描述符
close(events[i].data.fd);
} else {
cout << buf << endl;
}
}
代码实现
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <sys/epoll.h>
#include <crazy/net/Socket.h> // 个人封装的网络库,相当简单,未来会完善(说出来我自己都不信)
using namespace std;
int main() {
map<int, Socket> fdClient;
Socket server(8080); // 本地套接字(用我自己写的一个库封装了起来)
server.init(); // 初始化套接字
server.Bind(); // 绑定套接字
server.Listen(10); // 设置监听数量
int efd = epoll_create(10); // 创建epoll事件监听器,efd事件该事件监听器的文件描述符
struct epoll_event ev; // 一次性事件
struct epoll_event events[10]; // 事件列表,用来从epoll中取出事件
ev.events = EPOLLIN; // 仅当有可读事件事件发生唤醒
ev.data.fd = server.getFd(); // 将本地套接字放入事件结构体中
epoll_ctl(efd, EPOLL_CTL_ADD, server.getFd(), &ev); // 将准备好的事件放入监听列表中
while (true) { // 事件循环
int ret = epoll_wait(efd, events, 10, -1); // 用events从epoll中获取事件列表
for (int i = 0; i < ret; i++) {
if (events[i].data.fd == server.getFd()) { // 如果该套接字是本地套接字,那么证明有新链接
SocketData clientSock;
server.Accept(&clientSock);
ev.data.fd = clientSock.fd; // 给临时事件结构体赋值网络套接字
ev.data.fd == EPOLLIN | EPOLLET; // 设置要监听的事件
epoll_ctl(efd, EPOLL_CTL_ADD, clientSock.fd, &ev); // 将配置好的事件放入监听列表中
Socket client(clientSock); // 创建对应的客户端套接字(方便读写)
fdClient.insert(pair<int, Socket>(clientSock.fd, client));
} else { // 正常的I/O读写事件
char buf[100] = { 0 };
int ret = fdClient[events[i].data.fd].Read(buf, 100);
if (ret == 0) { // 如果read返回值为0的话,对等方关闭套接字
map<int, Socket>::iterator it = fdClient.find(events[i].data.fd); // 从映射列表中找到要删除的套接字
if (it != fdClient.end()) { // 确定有这个映射
epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL); // 从epoll中删除事件
close(events[i].data.fd); // 关闭回收文件描述符
fdClient.erase(it);
}
} else if (ret == -1) { // I/O异常
perror("peer connection error:");
} else { // 读到有效信息
cout << buf << endl;
fdClient[events[i].data.fd].Write(buf, strlen(buf)); // 并将收到的消息转发回去
}
}
}
}
server.Close(); // 关闭本地套接字
close(efd); // 关闭epoll文件描述符
return 0;
}
上面代码中用到的网络套接字封装我放在github中了 : crazy_net