网络编程中select、poll与epoll详解
在C/S中,存在多种I/O模型(详见历史文章—C/S编程中常见的I/O模型介绍)。select、poll与epoll用于其中的I/O复用模型中,其读写是同步阻塞的。相比普通的read、write操作,可以检测多个描述符。
文章目录
1.select函数
1.1 函数简介
select函数原型:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
const struct timeval *timeout);
// 返回:表示此时有多少个监控的描述符就绪,若超时则为0,出错为-1。
参数说明:
maxfdp1:感兴趣的描述符中,最大的数值加1,所以名字是max fd plus 1。
readset/writeset/exceptset:指定内核感兴趣的读、写和异常的描述符,若对某些参数不感兴趣就设置为空指针NULL。
timeout:时间结构体,指定select函数阻塞的时间,若超过该时间,则select返回0。
1.2 特性
· 当执行select函数时,系统会将select中设定的fd_set等内容复制到内核空间中,内核将进行监听设定的感兴趣文件描述符;
· 当select函数返回时,系统会将就绪描述符写入readset,writeset和exceptset中,并将其拷贝到用户空间中,并返回就绪描述符个数;
因此,每次调用都会来回拷贝fd_set中的内容,这对于高效网络编程来说无疑是致命的。同时,用户在处理返回的就绪描述符时,需要遍历整个监听的文件描述符集合fd_set,效率低下。
注意:select函数受fd_set的大小限制,可以通过cat /proc/sys/fs/file-max 进行查看,32位最多能监听的fd为1024个,64位最多监听2048个。
2.poll函数
2.1 函数简介
poll函数原型:
int poll(struct pollfd* fds, int nfds, int timeout);
/*
struct pollfd{
int fd; // 感兴趣fd
short events; // 监听事件
short revents; // 就绪事件
};
*/
// 返回:表示此时有多少个监控的描述符就绪,若超时则为0,出错为-1。
参数介绍:
fds:一个监听文件描述符数组的首地址,可以是pollfd数组名。
nfds:监听文件描述符数量。
timeout:超时时间设置,其取值如下:
timeout值 | 说明 |
---|---|
INTTM | 永远等待 |
0 | 立即返回,不阻塞进程 |
>0 | 等待指定数目的毫秒数 |
2.2 特性
poll函数与select原理相似,都需要来回拷贝全部监听的文件描述符。不同的是:
· poll函数采用链表的方式替代原来select中fd_set结构,因此可监听文件描述符数量不受限;
· poll返回后,可以通过pollfd中的内容进行处理就绪文件描述符,相比select效率要高;
3.epoll函数
3.1 函数简介
epoll_create函数介绍:
int epoll_create(int size); // 新建用于监听fd的根结点,其结构是一棵 红黑树
// 返回:建立的红黑树的根结点的文件描述符——epfd
epoll_create函数用于新建用于管理的监听fd。
参数说明:
size:目前linux版本已经忽略了size大小设置。
epoll_ctl函数介绍:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
/*
struct epoll_event{
uint32_t events; // epoll事件 EPOLLIN/EPOLLOUT EPOLLET/EPOLLLT
epoll_data_t data; // 用户数据
};
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
*/
epoll_ctl函数用于修改、删除、添加对应文件描述符的感兴趣事件。
参数说明:
epfd:用于管理epoll_event事件的红黑树的根结点,epoll_create的返回值。
op:需要修改的操作,由宏定义给出
宏定义 | EPOLL_CTL_ADD | EPOLL_CTL_DEL | EPOLL_CTL_MOD |
---|---|---|---|
说明 | 添加监听事件 | 删除监听事件 | 修改监听事件 |
fd:需要修改的文件描述符fd。
event:当前文件描述符感兴趣事件。
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
epoll_wait函数用于监听感兴趣文件描述符,并返回就绪文件描述符数量。
参数说明:
epfd:epoll_create返回值
events:需要监听的事件数组的首地址
maxevents:events数组的大小
timeout:与poll中的timeout相似
3.2 特性
· epoll解决了poll与select函数的弊端,不需要遍历就绪fd,可以直接通过epoll_event中的ptr设定事件处理函数;
· 不需要重复拷贝感兴趣的文件描述符表,而是通过建立共享内存,实现用户空间与内核空间之间的数据传递;
· 使用红黑树进行管理文件描述符,查找效率高;
· 只拷贝就绪文件描述符链表,而不是全部感兴趣文件描述符;
· epoll_data结构中存在union结构,用户可自定义对应fd的事件处理函数,提高事件响应速度;
· epoll存在两种触发机制;
3.3 epoll的实现机制
1.使用epoll_create新建一颗红黑树,用于管理感兴趣文件描述符,epoll使用共享内存实现用户空间与内核空间传输数据,因此不需要将感兴趣fd拷贝到内核空间中;
2.可以使用epoll_ctl注册对应fd感兴趣的事件,此时系统会将fd添加到红黑树中,并设置对应的激活回调函数;
3.当fd就绪,系统会调用激活回调函数,并将该fd写入到就绪fd链表中;
4.最后将就绪文件描述符链表拷贝回用户空间,epoll_wait返回。此时,用户便可以通过返回的就绪fd处理对应的事件。epoll在监听时,使用回调机制实现,而不需要进行轮询,因此效率较高。