- 1. 为什么要使用epoll?
- 1.1 基于select的I/O复用技术速度慢的原因
- 2. epoll函数的介绍
- 2.1 epoll_create
- 2.2 epoll_ctl
- 2.3 epoll_wait
- 3. epoll的使用
- 3.1 epoll总结
- 4. 条件触发和边缘触发
实现I/O复用的传统方法有select和poll,但这两者都无法实现同时接入上百个客户端,所以select并不适合当前以Web服务端开发为主流的现代开发环境,于是就产生了epoll。epoll是linux平台所支持的,在Windows平台对应支持的是IOCP。
1.1 基于select的I/O复用技术速度慢的原因- 调用select函数后,需要对所有文件描述符执行循环检查语句。
- 每次调用select函数时都需要向该函数传递监视对象信息。
循环语句不是最大的麻烦,每次传递监视对象信息到 *** 作系统中才是最大的麻烦。
应用程序向 *** 作系统传递数据将对程序造成很大负担,而且无法通过优化代码解决,因此将成为性能上的致命弱点。
select函数与文件描述符有关,更准确地说,是监视套接字变化的函数。而套接字是由 *** 作系统管理的,所以select函数绝对需要借助于 *** 作系统才能完成功能。select函数的这一缺点可以通过如下方式弥补:
"仅向 *** 作系统传递1 次监视对象,监视范围或内容发生变化时只通知发生变化的事项。”
这样就无需每次调用select函数时都向 *** 作系统传递监视对象信息,但前提是 *** 作系统支持这种处理方式(每种 *** 作系统支持的程度和方式存在差异)。Linux的支待方式是epoll, Windows的支持方式是IOCP 。
select 的优点
epoll方式只在Linux下提供支持,也就是说,改进的I/O复用模型不具有兼容性。相反,大部分 *** 作系统都支持select函数。只要满足或要求如下两个条件, 即使在Linux平台也不应拘泥于epoll 。
- 服务器端接入者少。
- 程序应具有兼容性。
epoll函数具有如下优点,这些优点正好与之前的select函数缺点相反。
- 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句。
- 调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息。
epoll服务器端实现中需要的3个函数,
- epoll_ create: 创建保存epoll文件描述符的空间。
- epoll_ctl: 向空间注册并注销文件描述符。
- epoll_wait: 与select函数类似,等待文件描述符发生变化。
select和epoll的不同
select方式
- 用fd_set变量保存监视对象文件描述符。
- 用FD_SET,FD_CLR添加和删除对象。
- select函数等待文件描述符的变化。
- 通过FD_ISSET查看状态变化的文件描述符。
epoll方式
- *** 作系统负责保存监视对象文件描述符,因此需要向 *** 作系统请求创建保存文件描述符的空间。
- 通过epoll_ctl 函数请求 *** 作系统添加和删除对象。
- epoll_wait函数等待文件描述符的变化。
- 通过结构体epoll_event将发生变化的文件描述符单独集中到一起。
epoll_event结构体:
struct epoll_event { uint32_t events; epoll_data_t data; } typedef union epoll_data { void * ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
声明足够大的epoll_even结构体数组后,传递给epoll_wait函数时.发生变化的文件描述符信息将被填入该数组。因此, 无需像select函数那样针对所有文件描述符进行循环。
2.1 epoll_create#includeint epoll_create(int size);
调用epoll_creat函数创建的文件描述符保存空间称为“epoll例程”,有些情况下名称会不同。通过传递size参数来决定空间大小,但这个值只是给 *** 作系统一个建议,换言之,size并不能决定epoll的大小。
Linux 2.6.8 之后的内核将完全忽略传入epoll_create 函数的size 参数,因为内核会根据情况调整epoll 例程的大小。
epoll_creat函数创建的资源与套接字相同,也由 *** 作系统管理。因此,此函数和创建套接字的情况相同,也会返回文件描述符。需要终止时,与其他文件描述符相同,也要调用close函数。
2.2 epoll_ctl生成例程后,应在其内部注册监视对象文件描述符,此时使用epoll_ctl函数。
#includeint epoll_ctl(int epfd, int op, int fd, struct epoll_event * event); //成功时返回0 , 失败时返回-1 。 #epfd 用千注册监视对象的epoll例程的文件描述符。 #op 用千指定监视对象的添加、删除或更改等 *** 作。 #fd 需要注册的监视对象文件描述符。 #event 监视对象的事件类型。
该函数看起来复杂,但是调用语句就很容易理解,假设按如下形式调用epoll_ctl函数:
epoll_ctl(A, EPOLL_CTL_ADD, B, C);
第二个参数 EPOLL_CTL_ADD 意味着“添加”,上述语句有如下意义:
epoll例程A中注册文件描述符B,主要目的是为了监视参数C中的事件。
再介绍一个调用语句。
epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);
上述语句中第二个参数意味着这是一条删除语句,其含义为:
从epoll例程A中删除文件描述符B
从示例可以看出,从监视对象中删除文件描述符时,不需要监视类型参数,因此可以向第四个参数传递NULL。
epoll_ctl第二个参数传递的常量及含义。
- EPOLL_CTL_ADD: 将文件描述符注册到epoll例程
- EPOLL_CTL_DEL: 从epoll例程中删除文件描述符。
- EPOLL CTL MOD: 更改注册的文件描述符的关注事件发生情况。
epoll_event结构体用于保存事件的文件描述符集合,但也可以在epoll例程中注册文件描述符时,用于注册监视事件。如下:
struct epoll_event event; event.events=EPOLLIN; //发生需要读取数据的情况(事件) 时 event.data.fd=sockfd; epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
上述代码将epfd注册到epoll例程epfd中,并在需要读取数据的情况下产生相应事件。下面是epoll_event的成员events中可以保存的常量及所指的事件类型。
- EPOLLIN: 需要读取数据的情况。
- EPOLLOUT: 输出缓冲为空,可以立即发送数据的情况。
- EPOLLPRI: 收到OOB数据的情况。
- EPOLLRDHUP: 断开连接或半关闭的情况,这在边缘触发方式下非常有用。
- EPOLLERR: 发生错误的情况。
- EPOLLET: 以边缘触发的方式得到事件通知。
- EPOLLONESHOT: 发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl 函数的第二个参数传递EPOLL_CTL_MOD , 再次设置事件。
可以通过位运算同时传递多个上述参数,如EPOLLIN | EPOLLOUT
2.3 epoll_wait下面是函数原型:
#includeint epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); //成功时返回发生事件的文件描述符数,失败时返回-1。 #epfd 表示事件发生监视范围的epoll例程的文件描述符。 #events 保存发生事件的文件描述符集合的结构体地址值。 #maxevents 第二个参数中可以保存的最大事件数。 #timeout 以1/1000秒为单位的等待时间,传递-1 时, 一直等待直到发生事件。
该函数调用方式如下。需要注意的是,第二个参数所指缓冲需要动态分配。
int event_cnt; struct epoll_event * ep_events; ep_events = malloc(sizeof(struct epoll_event}*EPOLL_SIZE); //EPOLL_SIZE 是宏常量 event cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
调用函数后,返回发生事件的文件描述符,同时在第二个参数指向的缓冲中保存发生事件的文件描述符集合。因此,无需像select一样插入针对所有文件描述符的循环。
3. epoll的使用客户端:
#include#include #include #include #include #include #include #define BUF_SIZE 100 #define EPOLL_SIZE 50 void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; socklen_t adr_sz; int str_len, i; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if(argc != 2){ printf("Usage : %s n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) error_handling("bind() error"); if (listen(serv_sock, 5) == -1) error_handling("listen() error"); epfd = epoll_create(EPOLL_SIZE); // 该参数可以忽略 ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE); event.events = EPOLLIN; // 监控读取数据的情况 event.data.fd = serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); // 在例程中添加 while(1){ event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if(event_cnt == -1){ puts("epoll_wait() error"); break; } for(i =0; i 编译运行:
3.1 epoll总结epoll使用的流程:
- epoll_create创建一个保存epoll文件描述符的空间,可以没有参数
- 动态分配内存,给将要监视的epoll_wait
- 利用epoll_ctl控制、添加、删除,
- 利用epoll_wait来获取改变的文件描述符,来执行程序。
select和epoll的区别:4. 条件触发和边缘触发
- 每次调用select函数都会向 *** 作系统传递监视对象信息,浪费大量时间
- epoll仅向 *** 作系统传递一次监视对象,监视范围或内容发生改变时只通知发生变化的事项。
epoll对文件描述符有两种模式:条件触发(Level Trigger),边缘触发(Edge Trigger)。
条件触发和边缘触发欢迎分享,转载请注明来源:内存溢出
评论列表(0条)