3.20-epoll 函数
epoll 函数
如果内存 1G, epoll 支持 100k 连接
- 不能跨平台
- 支持的并发量很大
IO 多路转接函数
- select
- 跨平台: 支持
- 在 Windows 平台: select的第一个参数是无意义的
- 在 linux 平台: 第一个参数的意义是最大的文件描述符 + 1
- 检测的连接数
- 最大 1024
- 检测方式和效率
- 线性检测, 文件描述符越多, 效率越低
- 使用 select 会发生多次数据拷贝
- 内核区拷贝到用户区
- 用户区拷贝到内核区
- 传出信息的量:
- 有多少描述符发生变化 -> 返回值
- 到底是谁发生变化需要手动检测内核传出的集合
- poll
- 跨平台: 不支持, 仅支持 linux
- 检测的连接数
- 和内存有关
- 检测方式和效率
- 线性检测, 文件描述符越多, 效率越低
- epoll
- 跨平台: 不支持, 仅支持 linux
- 检测的连接数
- 和内存有关
- 检测方式和效率
- 树状(红黑树)模型, 检测效率很高
- 委托 epoll 检测的文件描述符集合用户和内核使用的是同一块内存, 没有数据的拷贝
- 使用了共享内存
- 传出信息的量:
- 有多少描述符发生变化 -> 返回值
- 可以精确地知道是哪个文件描述符发生了变化
epoll 的使用
// epoll 使用步骤
// epoll 是一个模型, 使用 epoll 需要调用三个函数
/*
1. 需要创建一个树状模型, 没有节点
2. 要检测的节点添加到 epoll 树上
文件描述符的类型:
- 监听的
- 通信的
从检测的事件上说:
- 读
- 写
3. 开始委托内核对树上的节点进行检测
4. 处理的过程
- 监听的: 建立新的连接
- 通信的: 接受和发送数据
*/
- 函数
#include <sys/epoll.h>
// 创建一个 epoll 模型
int epoll_create(int size);
size:
该参数无意义, > 0 即可
返回值:
- 成功: 返回一个有效的文件描述符. 可以理解为红黑树的根节点, 通过这个文件描述符就可以访问创建的实例
- 失败: -1, errno
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
- events:
- EPOLLIN: 读事件, 检测文件描述符的读缓冲区
- EPOLLOUT: 写事件, 检测文件描述符的写缓冲区
- data.fd:
使用 epoll_ctl 的第三个参数
union epoll_data {
void *ptr;
int fd; // 常用的一个成员
uint32_t u32;
uint64_t u64;
};
// 对 epoll 的操作函数
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *_Nullable event);
参数:
- epoll_create() 的返回值, 找到 epoll 树的实例
- op:
- EPOLL_CTL_ADD: 添加新节点
- EPOLL_CTL_MOD: 修改已经添加到树上的节点
- 如原来检测的是读, 可以改为写事件
- EPOLL_CTL_DEL: 删除
- fd: 要操作的文件描述符
- 如何操作: 添加 / 修改 / 删除
- 种类:
- 监听的
- 通信的
- event:
- 添加: 设置要检测的文件描述符的事件
- 修改: 修改对应
- 删除: NULL
// 阻塞函数
// 委托内核检测 epoll 树上的文件描述符的状态, 没有状态变化, 该函数一直阻塞
// 有满足条件的文件描述符被检测到, 函数返回
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
参数:
- epoll_creat 的返回值, 找到 epoll 的实例
- events: 传出参数, 记录了当前一轮检测所有的发生变化的文件描述符信息
- 这个参数是一个结构体数组的地址
- maxevent: 指定第二个参数的容量
- timeout: 超时时长, -1 阻塞, 0 不阻塞
返回值:
成功: 有多少个文件描述符发生了变化
int main()
{
// 1. 创建监听的套接字
int lfd = socket();
// 2. 绑定
bind();
// 3. 设置监听
listen();
// 4. 创建 epoll 模型
int epfd = epoll_create();
// 5. 将要检测的文件描述符添加到 epoll 模型中
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, epoll_ctl_add, lfd, &ev);
// 6. 开始检测
struct epoll_event events[1024];
while (1)
{
int num = epoll_wait(epfd, events, 1024, -1);
// 处理 num 个有状态变化的文件描述符
for (int i= 0; i < num; i++)
{
int curfd = events[i].data.fd;
if (curfd == fd)
{
int cfd = accept(lfd, NULL, NULL);
// cfd 添加到 epoll 中
ev.events = EPOLLIN;
ev.data.fd = cfd;
epoll_ctl(epfd, epoll_ctl_add, cfd, &ev);
}
else
{
// 通信
int len = recv(curfd, buf, size - 1)
if (len == 0)
{
printf("客户端断开连接\n");
// 删除
epoll_ctl(epfd, epoll_stl_del, curfd, NULL);
colse(curfd);
}
else if (len > 0)
{
send
}
else
{
perror("recv");
exit(0);
}
}
}
}
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
int main()
{
// 1. 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1)
{
perror("socket");
exit(0);
}
// 2. 绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET; // ipv4
addr.sin_port = htons(8989); // 转网络字节序
addr.sin_addr.s_addr = INADDR_ANY; // 0 地址
if (-1 == bind(lfd, (struct sockaddr*)&addr, sizeof addr))
{
perror("bind");
exit(0);
}
// 3. 设置监听
if (-1 == listen(lfd, 128))
{
perror("listen");
exit(0);
}
// 4. 创建 epoll 模型
int epfd = epoll_create(100);
if (epfd == -1)
{
perror("epfd");
exit(0);
}
// 5. 将要检测的节点添加到 epoll 模型中
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev))
{
perror("epoll_ctrl");
exit(0);
}
struct epoll_event evs[1024];
int size = sizeof evs / sizeof evs[0];
// 6. 不停地委托内核检测 epoll 模型中的文件描述符状态
while (1)
{
int num = epoll_wait(epfd, evs, size, -1);
printf("num = %d\n", num);
for (int i = 0; i < num; i++)
{
// 取出当前元素中的文件描述符
int curfd = evs[i].data.fd;
if (curfd == lfd)
{
// 建立新连接, 本次调用绝对不会阻塞
int cfd = accept(lfd, NULL, NULL);
ev.events = EPOLLIN;
ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
}
else
{
char buf[1024];
memset(buf, 0, sizeof buf);
int len = recv(curfd, buf, sizeof buf, 0);
if (len == 0)
{
printf("连接中断\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
}
else if (len > 0)
{
printf("recv: %s\n", buf);
send(curfd, buf, len, 0);
}
else
{
perror("recv");
exit(0);
}
}
}
}
close(lfd);
return 0;
}