高级IO:
五种IO模型:阻塞IO; 非阻塞IO; 信号驱动IO;异步IO;多路转接IO
IO操作分为两个过程:等待/数据拷贝
阻塞IO:
发起IO调用后,若不具备IO条件,则等待IO条件具备,拷贝数据后返回
非阻塞IO:
发起IO调用后,若不具备IO条件,则立即报错返回,若具备IO条件则立即拷贝数据后返回
信号驱动IO:
先定义IO信号处理,若IO条件具备则直接信号通知进程,发起调用,拷贝数据后返回
异步IO:
先定义IO信号处理,发起异步IO调用,直接返回。让别人进行IO等待,等待IO条件具备后拷贝数据
(与信号驱动的区别是拷贝数据是由别人来完成的)信号通知进程IO完成
**
阻塞:为了完成功能发起调用,如果当前不具备完成条件,则一直等待,直到完成后返回。
非阻塞:为了完成功能发起调用,如果当前不具备完成条件,直接报错返回;通常需要循环处理
阻塞与非阻塞:调用功能当前不具备完成条件是否立即返回。
同步:为了完成功能发起调用,如果当前不具备完成条件,则自己等待完成功能后返回
异步:为了完成功能发起调用,但是功能由别人来完成
同步与异步的区别:功能是否由自己来完成
同步操作通常都是阻塞操作
异步包含两种:
异步阻塞操作:等待别人完成操作
异步非阻塞操作:不等待别人完成操作
同步与异步的优缺点:
同步:流程控制简单,但是效率相对较低
异步:流程控制较难,但是效率相对较高
多路转接IO:(多路复用IO)
功能:是一种IO事件的监控,同时对大量的描述符进行事件监控,监控描述符是否具备IO条件;
作用:通过IO多路转接技术监控描述符,就可以得到描述符的状态
适用场景:有大量的客户端连接但是同一时间只有少量活跃的情景,并且每一个客户端的请求处理时间不会很长
模型:select模型/poll模型(被淘汰)/epoll模型
都是实现对大量描述符进行事件监控的操作
就绪:
对于可读事件来说,缓冲区中有数据到来就是读就绪;
(接收缓冲区中的数据大小大于低水位标记(一个字节),就会触发刻可读事件)
对于可写事件来说,缓冲区中有空闲空间就会写就绪;
(发送缓冲区中的空闲空间大小大于低水位标记(一个字节),就会触发刻可写事件)
select模型:三个功能(监控/添加描述符/移除描述符)
通过对大量事件集合中的描述符阻塞进行各自的事件监控,当对应集合中有描述符事件就绪/超时则返回
事件就绪表示:描述符当前可读/可写/异常
(返回之前将集合中没有就绪的描述符全部删除)
(描述符从小到大排列,包含了系统中所有的描述符)
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
nfds:等于监控的描述符中,最大的那个描述符+1;
readfds:读事件集合
writefds:写事件集合
exceptfds:异常事件集合
fd_set:描述符集合
timeout:select等待的超时时间
返回值:小于0—>监控出错 等于0—>等待超时 大于0—>当前有多少个描述符就绪
1.定义fd_set:描述符集合(是一个位图,位图大小取决于_FD_SETSIZE = 1024)
(由select完成)
2.将集合拷贝到内核进行监控,监控的原理是对所有的描述符进行轮询遍历状态
3.当有描述符就绪的时候,在调用返回之前将集合中没有就绪的描述符剔除出去
4.用户操作:对所有的描述符进行遍历,查看哪一个还在集合中,那么这个描述符就已经就绪
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); //清空描述符集合
(使用C++封装select类)——代码实现
优点:
1.select遵循POSIX,可以跨平台——移植性强
2.监控的超时时间更加精细——微妙
缺点:
1.所能监控的描述符是有上限的(默认:1024,取决于_FD_SETSIZE = 1024)
2.select实现监控原理是在内核中进行轮询遍历状态,因此性能会随着描述符增多而下降
3.select每次监控返回时会修改描述符集合(移除未就绪的描述符),需要每次监控时重新添加到描述符集合中
4.select要监控的集合中的描述符数据,需要每次重新向内核中拷贝
5.不会告诉用户哪一个描述符就绪,只是告诉用户有就绪事件,需要用户遍历查找
poll模型:(被淘汰的技术)
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
fds:事件数组
nfds:监控事件个数
timeout:超时等待时间
监控实现原理:
1.用户定义一个事件数组,对描述符可以添加关心的事件,进行监控
// pollfd结构
struct pollfd {
int fd; /* 用户监控的文件描述符 */
short events; /* 保存用户关心的事件 */
/*(POLLIN / POLLOUT)*/
short revents; /* 保存当前就绪事件 */
};
2.poll实现监控的原理也是将事件结构拷贝到内核,然后进行轮询遍历监控,性能随着描述符的增多而下降
3.若有描述符就绪,则修改这个响应描述符事件结构中的实际就绪事件
(用户完成:)
4.用户根据返回的revents判断哪一个事件就绪,然后进行操作即可
5.poll也不会告诉用户哪一个描述符就绪,只是告诉用户有就绪事件,需要用户遍历查找
优点:
1.采用事件结构的方式对描述符进行监控,简化了多个事件集合的监控方式
2.没有描述符的具体监控上限
缺点:
1.不能跨平台
2.poll采用轮询遍历的方式判断就绪,性能随着描述符的增多而下降
3.不会告诉用户哪一个描述符就绪,只是告诉用户有就绪事件,需要用户遍历查找
epoll模型;
int epoll_create(int size);//创建epoll
功能:在内核中创建一个结构体
struct eventpoll{
rbr-红黑树
rdlist
};
size:—能监控的描述符上限自从linux2.6.8之后,size参数是被忽略的.只要大于0就可以
返回值:文件描述符-非负整数-epoll的操作句柄
int epoll_ctl();
功能:向内核中epfd对应的eventpoll结构体中进行添加/移除/修改一个fd描述符所关心的事件.
也是采用事件结构体的形式监控描述符
epfd: 返回的epoll操作句柄
EPOLL_CTL_ADD:从内核中的evnetpoll中添加要监控的事件
EPOLL_CTL_MOD:修改内核中要监控的事件结构体
EPOLL_CTL_DEL:从内核中的evnetpoll中移除要监控的事件
fd: 用户索要监控的描述符
event: 描述符对应所要监控的事件
EPOLLLT(边缘触发):
EPOLLET(水平触发:默认):
data:描述符就绪后会返回事件结构体,用户可以获得这个数据
int epoll_wait();
功能:开始监控
返回值:大于0-就绪的事件个数 等于0-超时等待 小于0-出错
eventpoll{
rbr-红黑树-保存用户添加的事件结构结点
rdlist-双向列表-
};
原理:
1.告诉内核要开始监控了(异步操作)
2.操作系统对描述符进行监控—采用的是事件触发方式进行监控,为每一个监控的描述符都定义
了一个事件,并且对这个事件定义了一个事件回调函数
3.这个事件回调函数做的事就是:将就绪的描述符所对应的epoll_event事件结构添加到双向链表rdlist中
4.epoll_wait并没有立即返回,而是每个一会就看一下内核中的eventpoll中的双向链表
(保存的都是就绪的描述符所对应的事件)是否为空(查看是否就绪)
5.若链表不为空,则表示描述符就绪,epoll_wait即将返回
(在返回之前,将就绪的描述符对应事件结构向用户的结构体数组(epoll_wait的第二个参数)拷贝一份)
6.epoll会将对应的就绪描述符拷贝一份到用户态直接告诉用户有哪些描述符就绪,进而可以直接对就绪的描述符进行操作
epoll的触发方式:
1.水平触发:
是要缓冲区中的数据大小大小低水位标记,就会触发可读/可写就绪事件
2.边缘触发:
可读事件:每次只有新数据到来的时候才会触发移除可读事件(不关注缓冲区中数据有多少
要求用户一次将缓冲区中的数据读取完全)
可写事件:每次只有缓冲区空闲空间从0变为大于低水位标记时才会触发可写事件
通常读写事件混合监控的时候对于可写事件就会使用边缘触发,
防止可写事件每次在不写如数据但是有空闲空间都会触发事件(但是又没有数据可写)
若是可读事件被设置为边缘触发 ,需要用户一次将所有的数据读取完毕,但是因为不知道数据还有有多少
因此只能循环从缓冲区读取数据,当循环读取但是缓冲区没有数据的时候,recv就会阻塞;
因此边缘触发的可读事件的描述符通常需要被设置为非阻塞
(设置非阻塞:)
int fcntl(int fd, int cmd, .../* arg */);
cmd:
F_GETFL 获取描述符状态属性信息
F_SETFL 设置
优点:
1.没有监控的描述符上限
2.采用事件结构的方式对描述符进行监控,简化了多个事件集合的监控方式
3.epoll是一个异步阻塞操作,发起调用让操作系统进行监控,操作系统采用事件回调的方式
对描述符进行监控,避免了遍历轮询,所以性能不会随描述符上增多而降低
4.epoll发起调用后进行等待—循环判断内核中的就绪的事件是否为空来确定是否有事件就绪
若有事件就绪,就将对应事件拷贝到用户态供用户操作
直接告诉用户哪些描述符就绪了,直接操作,没有空遍历,提高性能,简化代码
5.epoll描述符的事件结构只需要向内核拷贝一次(内核eventpoll结构体的红黑树中),不要要每次拷贝
缺点:
1.不能跨平台
2.超时等待时间只能精确到毫秒