select、poll、epoll三者优缺点对比
多路I/O函数 | 优点 | 缺点 |
select | 1.函数诞生时间早,跨平台性好,windows、linux、macOS、Unix、类Unix均支持 | 1.监听的文件描述符上限为1024;2.需要添加业务逻辑,来监听满足条件的fd,代码编程难度会提高(业务逻辑是指添加一个1024大小的数组,通过数组来管理满足条件的fd,详见代码) |
poll | 1.自带数组结构,可将监听和返回事件集合分离;2.可以拓展文件描述符监听上限,突破1024 | 1.不能跨平台,仅Linux支持;2.无法直接定位满足监听事件的文件描述符,编码难度较大 |
epoll | 1.解决了监听事件难于管理的问题,编程比较简单;2.监听事件上限可以突破1024 | 1.不能跨平台,仅Linux支持。 |
1. select
1.1 select输入参数说明
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
参数说明:
int nfds //监听的最大文件描述符+1,作为监听循环的上限
fd_set *read_fds, //监视的可读文件句柄集合。
fd_set *write_fds, //监视的可写文件句柄集合。
fd_set *excepr_fds, //监视的异常文件句柄集合。
struct timeval *timeout //本次select()的超时结束时间。
1.2 select函数使用方法
方法一:通过循环判断文件lfd+1到maxfd之间是否有读事件,来操作套接字的读和写。
缺点:效率比较低,在lfd+1到maxfd之间如果有文件关闭了,也会进入FD_ISSET的判断。
方法二:定义数组int client[1024],用来管理lfd+1到maxfd之间需要监听的文件,具体方法如下:
1.当lfd有读事件时会生成cfd,通过for循环查找靠前没有使用的client元素,使client[i]=cfd(未使用的client[i]会置-1);
2.在使用FD_ISSET之前,先判断client[i]是否大于0,大于0的情况才进行判断。
具体的操作如下:
参考链接:linux select函数详解
1.3 使用select函数实现多路I/O转发
详见底部代码
主要疑问:select函数实现的是一个非阻塞忙轮询的逻辑,accept函数为何不会阻塞?
原因很简单,下方代码在使用accept函数之前,首先通过FD_ISSET函数判断了listenfd是有读事件发生的,所以不阻塞。只有在listenfd没有读事件且使用accept函数的情况下,才会阻塞,通过条件判断规避了阻塞的情况。
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready < 0)
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* 说明有新的客户端链接请求 */
clie_addr_len = sizeof(clie_addr);
connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /* Accept 不会阻塞 */
FD_SET(connfd, &allset); /* 向监控文件描述符集合allset添加新的文件描述符connfd */
if (maxfd < connfd)
maxfd = connfd;
if (0 == --nready) /* 只有listenfd有事件, 后续的 for 不需执行 */
continue;
}
2. poll
2.1 poll函数输入参数说明
NAME
poll, ppoll - wait for som