I/O多路复用之epoll模型
1 先谈谈select模型和poll模型的问题
- 由于select模型中,socket是由一个32个元素的int型数组中元素的二进制位存储的(位图存储),所以就有一个最大值的限定,在32位的系统中数量为32 * 4 * 8 = 1024;poll模型为了解决这个问题,使用了链表结构存储socket。
- 除了上述的问题,poll模型和select模型都存在以下两个很大程度上影响性能的问题:
- 在调用select()函数或poll()函数之后,都要循环遍历所有socket查找需要处理的socket;
- 在调用sele()函数或poll()函数之前,都需要传递所有监视对象的信息。
- 为了解决上述两个问题,我们需要重新构造一种I/O复用模型,这个模型必须满足两点:
- 在监视范围发生变化时,只返回产生变化的对应socket;
- 只传递以此监视对象。
2 epoll模型
epoll模型的出现是为了解决select和poll模型留下的缺陷,使用epoll模型搭建服务器涉及到以下三个函数,函数原型分别如下:
extern int epoll_create (int __size) __THROW;
extern int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event) __THROW;
extern int epoll_wait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout);
1. epoll_create
参数:
- __size:这个参数在早期的Linux版本中用来指定监听的文件描述符的数量上限,但在Linux 2.6.8及以后的版本中,这个参数被忽略,因为内核会根据需要动态地分配资源。不过,为了兼容性和代码可读性,通常还是会传递一个合理的值。
返回值:
- 成功时返回一个新的文件描述符(epoll实例),用于后续的epoll_ctl和epoll_wait调用。
失败时返回-1,并设置errno以指示错误。
使用方法:
- 调用epoll_create创建一个epoll实例。
- 使用返回的文件描述符作为epoll_ctl和epoll_wait的第一个参数。
2. epoll_ctl
参数:
- __epfd:由epoll_create返回的文件描述符。
- __op:要执行的操作,可以是以下三个值之一:
- EPOLL_CTL_ADD:添加一个新的文件描述符到epoll实例中。
- EPOLL_CTL_MOD:修改已存在的文件描述符的事件监听设置。
- EPOLL_CTL_DEL:从epoll实例中删除一个文件描述符。
- __fd:要添加、修改或删除的文件描述符。
- __event:指向一个epoll_event结构的指针,该结构指定了要监听的事件类型(如读就绪、写就绪等)和与该文件描述符关联的数据(通常用于区分不同的事件源)。
返回值:
- 成功时返回0。
- 失败时返回-1,并设置errno以指示错误。
使用方法:
- 使用EPOLL_CTL_ADD将新的文件描述符添加到epoll实例中。
- 使用EPOLL_CTL_MOD修改已存在的文件描述符的监听设置。
- 使用EPOLL_CTL_DEL从epoll实例中删除文件描述符。
3. epoll_wait
参数:
- __epfd:由epoll_create返回的文件描述符。
- __events:指向一个epoll_event数组的指针,该数组用于存储从epoll实例中返回的事件。
- __maxevents:__events数组的大小,即最多可以返回的事件数量。
- __timeout:等待事件的超时时间(毫秒)。如果为-1,则表示无限等待,直到有事件发生。如果为0,则立即返回,不管是否有事件发生。
返回值:
- 成功时返回发生的事件数量。
- 如果超时且没有事件发生,则返回0。
- 失败时返回-1,并设置errno以指示错误。
使用方法:
- 调用epoll_wait等待事件的发生。
- 如果有事件发生,epoll_wait会将事件信息填充到__events数组中,并返回发生的事件数量。
- 遍历__events数组,处理每个事件。
3 epoll模型服务器实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<sys/epoll.h