epoll相关函数介绍
epoll大致实现思路
假设现在存有一个文件描述符表,该表中存有对应的用于监听连接请求的文件描述符lfd,则创建一个树状结构,该树状结构根节点对应着文件描述符表的3号位置(0.1.2位置为系统占用位),左节点为第4号位置,依次类推,直到所有节点填充完毕,在统一对该树状结构进行管理。
1.该树结构为红黑二叉树
2.挂载在树上的结构类型为结构体类型struct epoll_event
关于epoll的三个主要函数
1.int epoll_creae(int size);
该函数生成一个epoll专用的文件描述符,在树状结构中对应着根节点
size: epoll上能关注的最大描述符数,用于控制某个epoll文件描述符事件,可以注册、修改、删除,当实际空间超过该声明时候,系统将自动拓展其空间
2.文件描述符控制函数
*int epoll_ctl(int epfd, int op, int fd,struct epoll_event event);
用于控制某个epoll文件描述符事件,可以注
册、修改、删除
函数参数
epfd:epoll_create生成的epoll专用描述符
op:对应操作宏
EPOLL_CTL_ADD – 注册
EPOLL_CTL_MOD – 修改
EPOLL_CTL_DEL – 删除
fd: 关联的文件描述符
event: 告诉内核要监听什么事件
3.等待IO事件发生 - 可以设置阻塞的函数
int epoll_wait(
int epfd;
struct epoll_event*events //数组
int maxevents
int timeout
);
对应select和poll函数
函数参数:
epfd:要检测的句柄
events:用于回传待处理事件的数组
maxevents:告诉内核这个events的大小
timeout:为超时时间
-1:永久阻塞
0:立即返回
简单的epoll模型代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char* argv[])
{
if(argc < 2)
{
printf("eg: ./a.out port\n");
exit(1);
}
struct sockaddr_in serv_addr;
socklen_t serv_len = sizeof(serv_addr);
int port = atoi(argv[1]);
// 创建套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器 sockaddr_in
memset(&serv_addr, 0, serv_len);
serv_addr.sin_family = AF_INET; // 地址族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
serv_addr.sin_port = htons(port); // 设置端口
// 绑定IP和端口
bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
// 设置同时监听的最大个数
listen(lfd, 36);
printf("Start accept ......\n");
struct sockaddr_in client_addr;
socklen_t cli_len = sizeof(client_addr);
// 创建epoll树根节点
int epfd = epoll_create(2000);
// 初始化epoll树
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event all[2000];
while(1)
{
// 使用epoll通知内核fd 文件IO检测
int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
// 遍历all数组中的前ret个元素
for(int i=0; i
epoll的三种工作模式
当客户端一次发送的数据超出了缓冲区大小,epoll在不同的工作模式下读取这些数据的方式也不同可分为
1.水平触发模式(默认的工作模式)
*只要fd对应的缓冲区有数据,epoll在读取完一个缓冲区大小的数据后会立刻返回在读取一次缓冲区数据,直到缓冲区的数据全部读取完成,epoll函数返回的次数和客户端发送数据的次数没有关系,该工作模式是epoll默认的工作模式
2.边沿触发模式
*客户端发送一次数据,epoll_wait则返回一次,读取的是上次缓冲区中还未读取完的数据,不在乎数据是否读取完,需要将循环里的temp.events = EPOLLIN改为temp.events = EPOLLIN|EPOLLET
3.边沿非阻塞触发模式
是效率最高的一种工作模式
具体设置
open()
设置flag
权限位为O_WDRW|O_NONBLOCK
终端文件:/dev/tty
fcntl
int flag = fcntl(fd,F_GETFL);
flag | = O_NONBLOCK;
fcntl(fd,F_SET,flag);
将缓冲区的数据全部读取
while(recv()>0)
{
printf();
}
参考代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char* argv[])
{
if(argc < 2)
{
printf("eg: ./a.out port\n");
exit(1);
}
struct sockaddr_in serv_addr;
socklen_t serv_len = sizeof(serv_addr);
int port = atoi(argv[1]);
// 创建套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器 sockaddr_in
memset(&serv_addr, 0, serv_len);
serv_addr.sin_family = AF_INET; // 地址族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
serv_addr.sin_port = htons(port); // 设置端口
// 绑定IP和端口
bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
// 设置同时监听的最大个数
listen(lfd, 36);
printf("Start accept ......\n");
struct sockaddr_in client_addr;
socklen_t cli_len = sizeof(client_addr);
// 创建epoll树根节点
int epfd = epoll_create(2000);
// 初始化epoll树
struct epoll_event ev;
// 设置边沿触发
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event all[2000];
while(1)
{
// 使用epoll通知内核fd 文件IO检测
int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
printf("================== epoll_wait =============\n");
// 遍历all数组中的前ret个元素
for(int i=0; i 0 )
{
// 数据打印到终端
write(STDOUT_FILENO, buf, len);
// 发送给客户端
send(fd, buf, len, 0);
}
if(len == 0)
{
printf("客户端断开了连接\n");
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
if(ret == -1)
{
perror("epoll_ctl - del error");
exit(1);
}
close(fd);
}
else if(len == -1)
{
if(errno == EAGAIN)
{
printf("缓冲区数据已经读完\n");
}
else
{
printf("recv error----\n");
exit(1);
}
}
#if 0
if(len == -1)
{
perror("recv error");
exit(1);
}
else if(len == 0)
{
printf("client disconnected ....\n");
// fd从epoll树上删除
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
if(ret == -1)
{
perror("epoll_ctl - del error");
exit(1);
}
close(fd);
}
else
{
// printf(" recv buf: %s\n", buf);
write(STDOUT_FILENO, buf, len);
write(fd, buf, len);
}
#endif
}
}
}
close(lfd);
return 0;
}
*如何让文件描述符突破1024的限制
1.在程序中
select 突破不了,需要编译内核,通过数组实现,
poll和epoll可以突破1024的限制
poll内部的链表
epoll红黑树
2.在系统中
查看受计算机硬件限制的文件描述符上线
cat /proc/sys/fs/file-max
并通过修改配置文件调整其上限
vim /etc/security/limits.conf
添加如下信息
* soft nofile 8000
* hard nofile 8000