非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码,非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一 般只有特定场景下才使用。
信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作
阻塞与非阻塞:
阻塞:发起一个操作,若当前操作条件不满足则一直等待
非阻塞:发起一个操作,若当前操作条件不满足则报错返回
阻塞与非阻塞关联:通常都是操作接口特性
阻塞与非阻寨区别: 发起一个接口调用后,接口是否会立即返回同步与异步
同步:功能由进程自身完成,并且通常是串行化的。
异步: 功能并不由进程自身完成,而是由系统完成,完成不一定是串行的
同步与异步的关联: 通常用于讨论一个任务的完成流程
同步与异步的区别: 功能是否由当前执行流自身完成。
异步阻塞:发起操作后,功能由系统完成,进程执行流自身等待系统完成
异步非阻塞:发起操作后,功能由系统完成,操作会直接返回,并不会等待
多路转接模型:常用于高并发服务器中技术的使用
作用:针对大量描述符进行IO就绪事件
监控优点:
1.让进程能够仅针对就绪的描述符进行IO操作,提高了任务处理效率
2.避免进程因为对于未就绪描述符进行操作,而导致阻塞这种情况
具体技术实现: select, poll, epoll
mongoose这个库的搭建服务器实现思想: 单进程+多路转接模型
select模型
lO事件: 可读事件,可写事件, 异常事件
流程思想
1.定义指定IO事件的描述符集合
2.将需要对指定事件进行监控的描述符添加到指定集合中
3.将事件的描述符集合,拷贝到内核中,进行事件监控
1).对集合中的所有描述进行一次遍历(判断有没有就绪,以及挂起),若没有就绪则将描述符挂到内核的IO事件队列
2).若监控过程中,有某个描述就绪了所要监控的事件,则会唤醒进程的阻塞
3).被唤醒后,select会再次将描述符集合遍历一遍,将集合中没有就绪的描述符移除
4.select监控返回后,只需要判断哪个描述符还在集合中,哪个描述符就就绪了哪个事件
5.进程就可以根据就绪的不同事件对描述符进行不同的IO操作。
接口:
3.开始监控
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select接口一旦返回,就意味着这三个集合中,就只保留了就绪了指定事件的描述符
4.判断哪个描述符还在集合中,哪个描述符就就绪了哪个事件
int FD ISSET(int fd,fd set *set); 判断fd描述符,是否在set集合中
将select应用在tcp服务器搭建上。
搭建一个tcp服务器,会涉及到服务器端为每一个客户端连接都会创建一个新的套接字,进行通信,需要需要对大量的描述符进行IO操作
但是因为之前,有可能会因为对没有就绪的描述符进行操作而导致程序流程阻塞
因此当时的解决方案:多执行流解决方案 (为每一个客户端的通信都创建执行流)
而最好的解决方案:多路转接模型+线程池
如何在单执行流中对大量描述符进行轮询处理
poll:操作流程
1.定义一个事件结构体数组
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd {
int fd; /* file descriptor */监控的文件描述
short events; /* requested events */想要监控的事件,POLLIN可读,POLLOUT可写
short revents; /* returned events */监控返回后,储存实际就绪的事件
};
2.如果描述符需要监控什么事件,就在数组进行设置
epoll
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
优点: