IO多路复用API总结
IO 多路复用概述
I/O 多路复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。
在IO多路复用技术描述前,先讲解下同步,异步,阻塞,非阻塞的概念。
网络IO模型
linux网络IO中涉及到的模型如下:
(1)阻塞式IO
(2)非阻塞式IO
(3)IO多路复用
(4)信号驱动IO
(5)异步IO
今天不谈信号驱动IO,略过..
同步/异步
在学习IO模型的时候,我们必须明确一个概念,处理 IO 的时候,阻塞和非阻塞都是同步 IO。
只有使用了特殊的 API 才是异步 IO,例如Linux网络中的AIO。
再看下POSIX对同步和异步这两个术语的定义:
- 同步IO操作:导致请求进程阻塞,直到I/O操作完成;
- 异步IO操作:不导致请求进程阻塞;
通俗的理解下同步和异步
- 同步:当执行系统调用read时,需要用户等待内核完成从内核缓冲区到用户缓冲区的数据拷贝。
- 异步:当执行异步IO操作例如
aio_read
时,用户不需要等待,只需要接收内核完成操作的通知,由内核来完成数据的读取。
阻塞/非阻塞
在知晓阻塞和非阻塞都是同步 IO后,阻塞和非阻塞就很好理解了
阻塞IO:由系统调用read,导致线程一直等待数据返回。
阻塞等待模型
非阻塞IO:系统调用read后立即返回一个状态,当数据达到内核缓冲区之前都是非阻塞的,即返回一个系统调用状态。
非阻塞等待模型
闪客的动图做的非常的形象,上述gif动图来源「低并发编程」
IO多路复用
IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;
select
select 是操作系统提供的系统调用函数,select()用来等待文件描述词(普通文件、终端、伪终端、管道、FIFO、套接字及其他类型的字符型)状态的改变。是一个轮循函数,循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行。
通过select,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理:
select原理
头文件
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
select调用
拥塞函数,拥塞等待文件描述符事件的到来
int select(int maxfdp
, fd_set *readset
, fd_set *writeset
, fd_set *exceptset
,struct timeval *timeout);
参数说明:
maxfdp:被监听的文件描述符的最大值,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的;
readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。
timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间,timeout == 0,select立即返回
timeval结构体
struct timeval
{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
};
select置位
int FD_ZERO(int fd, fd_set *fdset); //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset); //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set); //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位
当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零
调用 select函数,拥塞等待文件描述符事件的到来 ;如果超过设定的时间,则不再等待,继续往下执行
select返回后,用FD_ISSET测试给定位是否置位:
if(FD_ISSET(fd, &rset)
{
...
//do something
}
fd_set结构体
fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。
select使用
整个 select 的流程图如下:
Demo1: 基于select的点对点通信
基于select的点对点通信
简易聊天室select版本
运行效果如下:
简易聊天室select版本
完整代码阅读全文转跳或者发送文末关键字..
poll调用
Poll就是监控文件是否可读的一种机制,作用与select一样。
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数说明
struct pollfd
fds:是一个struct pollfd结构类型的数组,列出了我们需要poll()检查的文件描述符
typedef struct pollfd {
int fd; /* 需要被检测或选择的文件描述符*/
short events; /* 对文件描述符fd上感兴趣的事件 */
short revents; /* 文件描述符fd上当前实际发生的事件*/
} pollfd_t;
events:想要监听的事件
revents:实际上发生的事件
POLLIN
POLLOUT
POLLPRI
POLLRDHUB
POLLHUP
POLLERR
nfds
指定了fds中元素的个数,nfds_t为无符号整形
timeout
决定阻塞行为,一般如下:
- -1:一直阻塞到fds数组中有一个达到就绪态或者捕获到一个信号
- 0:不会阻塞,立即返回
- >0:阻塞时间
返回值
- >0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;
- ==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时
- -1:poll函数调用失败
poll使用
#include <stdio.h>
#include <poll.h>
#include <string.h>
int main()
{
int timeout = 0;
char buf[1024];
struct pollfd fd_poll[1]; //设置只有一个事件
while(1){
fd_poll[0].fd = 0;
fd_poll[0].events = POLLIN;
fd_poll[0].revents = 0;
memset(buf, '