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