1. IO模型
IO模型是指四种不同的文件读写方式.
(1) 阻塞IO
阻塞IO是最常用,最简单,效率最低的一种IO模型.
阻塞读
如果有数据可读,则直接读取数据
如果没有数据可读,则读会阻塞,直到读取到数据 或 出错才返回
阻塞写
如果有空间可供写入,则立即写入数据并返回
如果空间已满在执行写入时,则写会阻塞,直到有空间 或 出错 才返回
(2) 非阻塞IO
可以防止进程或线程阻塞在IO操作上, 但是无法确保IO能够准确完成相应的操作.
非阻塞读
如果有数据可读,则直接读取数据并返回
如果没有数据可读,也会立即返回 (0)
非阻塞写
如果有空间可供写入,则直接写入数据并返回
如果没有空间可写,也会立即返回 (0)
==> 对于非阻塞IO来说,有时候为了能够读取/写入数据,可能需要进行轮询.
例子 :
ssize_t timedread(int fd,void * buf,int count ,int timeout) { while((ret == read(fd,buf,count) == 0) { usleep(1000); if(timeout-- <= 0) { break; } } return ret; }
如何设置文件描述符 是 阻塞IO 还是 非阻塞IO ?
阻塞IO 和 非阻塞IO 是由文件文件描述符的 O_NONBLOCK 标志来决定的
如果在open时,flag参数中 位或 了 O_NONBLOCK 则文件描述符为非阻塞
如果在open时, flag参数中没有O_NONBLOCK 的标志,则文件描述符默认(阻塞)
(3) 异步通知
在文件可读或可写的时候, 有驱动向系统发送一个SIGIO信号来提醒进行.
(4) IO多路复用
允许同时对多个阻塞IO进行控制操作
提前监视这些阻塞IO 是否 已经可读,可写或出错, 当条件满足后,再去执行相应的读写操作.
select poll epoll ==> 用来监视文件描述符的
2. select
#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> fd_set : 文件描述符的集合. "类型" API函数接口 : FD_SET(fd,fd_set);//把一个文件描述符fd加入到fd_set这个集合中去 FD_CLR(fd,fd_set);//把一个文件描述符fd从集合中fd_set中清掉 FD_ZERO(fd,fd_set);//把一个集合fd_set全部清掉 FD_ISSET(fd,fd_set);//判断文件描述符fd是否在fd_set这个集合中. int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); nfds : 一般填你感兴趣的最大文件描述符 +1 readfds : 要监听读的文件描述符的集合; writefds: 要监听写的文件描述符的集合; exceptfds : 要监听出错的文件描述符的集合; 在调用前,调用者要填入你 "你感兴趣的文件描述符" 在函数返回后,三个集合中, 是 "是已经就绪的文件描述符" timeoutv: 超时时间设置. struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; 在调用前, 调用者要填入的是 "设定的超时时间" 在函数返回后, 这个timeout指向的是剩余时间. 返回值 : > 0 :表示已经就绪的文件描述符的个数. = 0 : 表示超时. < 0 : 表示出错啦 select的实现是在内核中开辟了一个内核线程去实现 : 同时监听的 . "轮询" : 轮流询问 while(不超时) { 先问一遍 [0 , 你感兴趣的文件描述符最大的那个) //[0,nfds) if(有文件就绪) { break; } sleep t; } 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);
例子 :
- select 延时的效果
struct timeval timeout; timeout.tv_sec = 5; tiemtout.tv_usec = 0; select(100,NULL,NULL,NULL,&timeout); <==> sleep(5); <==> usleep();
3.poll
pool的功能和select类似, “监听多个文件描述符 , 是否就绪”.
只不过poll用一个结构体 struct poolfd 来描述 , ‘‘监听请求’’
#include <poll.h> struct pollfd { int fd; //指定要监听的文件描述符 short events; //监听的事件 在linux内核中, 事件用bit-fields POLLIN : 可读的事件 POLLOUT : 可写的事件 POLLERR : 出错的事件 ... 如 : POLLIN | POLLOUT short revents; //已经就绪的事件 如 : 是否已经可读 if(revents & POLLIN) { 可读的 } }; 监听一个文件描述符, 就需要一个 struct pollfd 的结构体 监听多个文件描述符, 就需要多个 struct pollfd 的结构体 .... int poll(struct pollfd *fds, nfds_t nfds, int timeout); fds : 监听结构体 struct pollfd 的数组 nfds :表示第一个参数 struct pollfd数组的元素个数 timeout : 超时时间.单位 ms 返回值 : > 0 表示就绪的文件个数 = 0 表示超时 < 0 出错啦
4.epoll : man epoll
(1) epoll_create : 创建一个监听文件的集合,这个函数的返回值是一个文件描述符
NAME epoll_create - open an epoll file descriptor SYNOPSIS #include <sys/epoll.h> int epoll_create(int size); size : 已经被忽略了. 但是 > 0 返回值 : 成功 返回一个epoll的实例(对象),就是一个文件描述符 失败 返回-1,同时errno被设置
创建一个epoll的实例, 用来监听多个其他的文件描述符
那么要监听的文件描述符, 如何加入这个监听的实例中去呢?
(2) epoll_ctl
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epfd : epoll监听集合的文件描述符, epoll_create 返回值 op : 具体是何种操作, 常用的有如下 : EPOLL_CTL_ADD : 增加. 把一个要监听的文件描述符及监听事件 加入到epoll实例中去. EPOLL_CTL_DEL : 从监听集合中删除一个文件描述符. EPOLL_CTL_MOD : modify 修改个月已经在监听集合中的文件描述符的监听事件. fd : 要监听或删除或修改的 文件描述符 event : 要监听的事件的结构体的指针. 在epoll中,用一个结构体 struct epoll_event 来描述一个要监听的事件. struct epoll_event { uint32_t events; 要监听的事件标志, bit-fields 可用的监听事件有如下 : EPOLLIN :可读的事件 EPOOLOUT : 可写的事件 EPOLLRDHUP : 这个事件 用来监听 流式套接字(tcp sockfd) 对方是否已经关闭或关闭写 RPOLLERR : 出错事件 ... EPOLLET : Edge-Triggered 边缘触发 LT : Level-Triggered 只要有数据,就会不同的上报可读事件 EPOLLIN 默认行为 : LT ET : Edge-Triggered 有数据变化(数量),才报告可读事件 例子 : 一个文件里面有 2K数据 报告可读的事件. 这个时候, 你读走了1K数据,文件剩余1K. LT : ---> 不停地上报可读事件 ET : ----> 在数据有变化的情况下才会上报. epoll_data_t data; /* User data variable */ //用来保存用户的一些数据 }; typedef union epoll_data { void *ptr;//用户的数据指针 int fd;//文件描述符 uint32_t u32;// uint64_t u64;// } epoll_data_t; 返回值 : 成功 返回 0 失败 返回-1,同时errno被设置;
(3) epoll_wait : 用来等待监听事件的发生
NAME epoll_wait - wait for an I/O event on an epoll file descriptor SYNOPSIS #include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epfd : epoll实例 events :结构体数据,用来保存已经就绪事件的信息的. maxevents : 表示第二个参数结构体数组最多可以保存多少个事件结构体. timeout :超时时间 , 单位 为 ms 返回值 : > 0 已经就绪的文件描述符的个数 = 0 表示超时了 = -1 出错了
总结 :
-
select 实现原理
select函数在调用时,会从文件描述符 0 - nfds - 1 依次轮询遍历这些文件描述符在 readfds,writefds,exceptfds这是三个集合中存在是否就绪,如果没有就绪,则会在timeout指定的超时时间内一直循环遍历, 如果有就绪的,则在在当次轮询完会立即返回.
-
poll实现原理
poll是通过struct pollfd结构体数组的形式来指定并感兴趣的文件描述符是否就绪,该结构体中的 events成员用来指定感兴趣的事件,revents是用来存放以及就绪的事件,poll是通过指定的时间内,不断的去询问fds中的指定的文件描述符是否就绪
===> 单次轮询次数为nfds ,比select少
-
epoll实现原理
epoll是以对象的形式来描述一些你感兴趣点额文件描述符
==> epoll对象(感兴趣的文件描述符对象)
epoll支持文件描述符数量很多的情况下,效率要比select/poll
epoll支持 ET