一、IO多路复用 -- select()
结构体
-
fd_set:描述符集合(long类型数组)
函数
-
FD_ZERO():清空描述符集合 -
FD_SET():设置监听的描述符(把监听的描述符设置为1) -
FD_ISSET():判断描述符是否设置(判断描述符是否设置为1) -
FD_CLR():清除监听的描述符(把监听的描述符设置为0) -
select():监听描述符事件,如果描述符集合中没有就绪,等待;反之,函数返回,把描述符集合清空,并设置已经就绪的描述符(设置为1)。
就绪条件

编码流程
- 定义描述符集
- 清空描述符集
- 设置指定的描述符并获取最大的描述符值+1
- 等待描述符就绪
- 判断已就绪的描述符,并做对应处理。
代码结构
// 定义描述符集
fd_set rset;
// 清空描述符集
FD_ZERO(&rset);
// 设置描述符
FD_SET(fd1,&rset);
FD_SET(fd2,&rset);
// 获取最大描述符+1
int maxfdp1 = max(fd1,fd2) + 1;
// 等待描述符就绪
if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0){
// 判断已就绪的描述符
if(FD_ISSET(fd1,&rset)){
// do somthing
}
if(FD_ISSET(fd2,&rset)){
// do somthing
}
}
注意
-
fd_set可容纳最大描述符数为FD_SETSIZE。 - 每一次
select()前,必须重新设置描述符,如果设置了新的描述符,需要重新计算maxfdp1。
二、IO多路复用 -- poll()
结构体
struct pollfd
| 成员 | 含义 |
|---|---|
fd |
描述符 |
events |
监听事件,主要用于设置监听事件 |
revents |
实际触发的事件,用于判断触发的事件 |
函数
poll()
编码流程
- 定义
pollfd结构体数组 - 初始化
pollfd结构体数组 - 设置监听poll事件
- 等待poll事件
- 判断触发事件的描述符,并做对应处理。
触发事件

代码结构
// 定义pollfd结构体数组
struct pollfd pollfds[OPEN_MAX];
// 初始化pollfd结构体数组
int i;
for(i=0;i<OPEN_MAX;i++){
pollfds[i].fd = -1;
}
int pollfds_cnt = 0;
// 设置监听事件
pollfds[0].fd = fd1;
pollfds[0].event = POLLRDNOM;
pollfds_cnt++;
pollfds[1].fd = fd1;
pollfds[1].event = POLLRDNORM;
pollfds_cnt++;
// 等待poll事件
if(poll(pollfds,pollfds_cnt,INFTIM)>0){
int i;
for(i=0;i<pollfds_cnt;i++){
// 判断触发事件的描述符
if(pollfds[i].fd == fd1 && pollfds[i].revent & POLLRDNORM){
// do something
}
if(pollfds[i].fd == fd2 && pollfds[i].revent & POLLRDNORM){
// do something
}
}
}
注意
struct pollfd数组的最大数是OPEN_MAX(或者linux/fs.h中的INR_OPEN_MAX )
还可以通过如下方式查看:
cat /proc/sys/fs/file-maxulimit
IO多路复用 -- epoll()
结构体
struct epoll_event
| 成员 | 含义 |
|---|---|
data.fd |
描述符 |
events |
设置/获取epoll事件 |
函数
-
epoll_create():创建epoll描述符 -
epoll_ctl():控制epoll描述符 -
epoll_wait():等待epoll描述符
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,定义如下:
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据
struct epoll_event {
__uint32_t events; /* epoll event */
epoll_data_t data; /* User data variable */
};
其中events表示感兴趣的事件和被触发的事件,可能的取值为:
| 事件 | 含义 |
|---|---|
EPOLLIN |
文件描述符可读 |
EPOLLOUT |
文件描述符可写 |
EPOLLPRI |
文件描述符高优先级可读 |
EPOLLERR |
文件描述符发生错误 |
EPOLLHUP |
文件描述符被挂断 |
EPOLLET |
ET的epoll工作模式 |
编码流程
- 创建epoll描述符
- 注册epoll事件
- 等待epoll事件
- 判断触发epoll事件的描述符和事件
- 关闭epoll描述符
触发条件
- ET(Edge Triggered)模式--边沿触发
| 操作 | 触发条件 |
|---|---|
| 读 | 接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件 |
| 写 | 发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件 |
- LT(Level Triggered)模式--水平触发
| 操作 | 触发条件 |
|---|---|
| 读 | 接收缓冲区不为空,有数据可读,读事件一直触发 |
| 写 | 发送缓冲区不满,可以继续写入数据,写事件一直触发 |
代码结构
- LT
// 创建epoll描述符
int epollfd = epoll_create(OPEN_MAX);
// 注册epoll事件
struct epoll_event evt;
evt.data.fd = fd1;
evt.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd1,&evt);
evt.data.fd = fd2;
evt.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd2,&evt);
// 等待epoll事件
int evts_cnt = 2;
struct epoll_event evts[evts_cnt];
int fd_cnt = epoll_wait(epollfd,&evts,evts_cnt,-1);
int i;
for(i=0;i<fd_cnt,i++){
// 判断触发epoll事件的描述符和事件
if(evts[i].data.fd == fd1 && evts[i].events & EPOLLIN){
// do something
}
if(evts[i].data.fd == fd2 && evts[i].events & EPOLLIN){
// do something
}
}
// 关闭epoll描述符
close(epollfd);
- ET
ET模式的文件句柄必须是非阻塞的。
// 创建epoll描述符
int epollfd = epoll_create(OPEN_MAX);
// 注册epoll事件
struct epoll_event evt;
evt.data.fd = fd1;
fcntl(fd1,F_SETFL,O_NONBLOCK);// fd1必须是非阻塞的
evt.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd1,&evt);
evt.data.fd = fd2;
fcntl(fd2,F_SETFL,O_NONBLOCK);// fd2必须是非阻塞的
evt.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd2,&evt);
// 等待epoll事件
int evts_cnt = 2;
struct epoll_event evts[evts_cnt];
int fd_cnt = epoll_wait(epollfd,&evts,evts_cnt,-1);
int i;
for(i=0;i<fd_cnt,i++){
// 判断触发epoll事件的描述符和事件
if(evts[i].data.fd == fd1 && evts[i].events & EPOLLIN){
// do something
}
if(evts[i].data.fd == fd2 && evts[i].events & EPOLLIN){
// do something
}
}
// 关闭epoll描述符
close(epollfd);
比较
select IO多路复用
- 缺点
- 只能监视
FD_SETSIZE个连接 - 不能确切指定有数据的
socket - 每次需要修改传入的
fd_set - 线程不安全
- 只能监视
poll IO多路复用
-
优点
- 不需要不修改传入的
pollfd数组 - 可以监视任意个连接
- 不需要不修改传入的
-
缺点
- 不能确切指定有数据的
socket - 线程不安全
- 不能确切指定有数据的
epoll IO多路复用
- 优点
- 能确切指定有数据的
socket - 线程安全
- 能确切指定有数据的