IO相关概念
IO在处理数据的时候可以分为两个阶段:数据准备阶段,数据读写阶段。根据IO在数据准备阶段的行为可以分为阻塞,非阻塞两种。
根据IO在数据读写阶段的行为可以分为同步,异步两种。
阻塞:
在等待数据时数据没来,调用IO方法的线程将进入挂起状态,系统一般默认IO都是阻塞状态。如在调用read,send方法时系统在该语句处等待数据到来。
非阻塞:
在等待数据时数据没来,不会改变线程状态,会根据当前数据状况直接返回一个值。一般通过fcntl系统调用更改设置。
同步:
在数据读写时需要由用户层自己操作整个读写过程,用户进程会阻塞直到数据读写完毕。
异步:
数据的读写是由内核操作完成,内核在数据读写完毕之后通知用户进程即可。
可以看出异步IO是一个非阻塞IO,而同步IO可以设置为阻塞IO用户进程忙等,也可以设置为非阻塞IO不断去轮询查看数据来了没有。这样看上去同步IO模型不是轮询就是忙等,效率似乎很低下。这时候就要搭配IO多路复用技术一起使用,简单来说就是在一个进程了同时监听多个IO口,这样cpu资源浪费就不会那么严重。
IO多路复用技术
select
思想:
1.构建一个文件描述符表,将要监听的文件描述符添加进去
2.调用系统函数,描述符进行了IO操作之后 ,函数返回【该函数阻塞】
3.返回时,通知进程有多少描述符进行了IO操作
缺点:
1.构建的文件描述符表每次都要从用户态拷贝到内核里去【消耗资源】
2.大小有限制,而且每次检测是否发生了读写事件需要线性遍历这个表
3.不可重用,这个描述符表在内核会被重置,没有发生事件置0。
poll
poll与select基本一样,但大小没有限制,而且在相应的结构体中引进变量revents来表明发生事件,检测文件描述符数组不变,每次只需恢复revents即可可重用。
epoll
1.epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样只需拷贝一次。
2.内部由一棵红黑树管理所有监听文件描述符,效率提高
3.读写事件发生时会将相应的事件存放到一个结构体中,不用遍历所有描述符
事件处理模式
事件处理模式决定了整个代码的框架与运行逻辑
Reactor模式
主线程只负责监听文件描述符上是否有实际事件发生,有事件发生则立即通知工作线程,将可读可写事件放入请求队列【传入套接字描述符即可】,由工作线程处理【接收连接,读写数据,处理客户请求】。
流程:
1.主线程往epoll内核事件表注册socket读就绪事件
2.主线程调用epoll_wait等待socket上有数据可读
3.有数据时,epoll_wait通知主线程。主线程将socket可读事件放入请求队列
4.请求队列中的工作线程被唤醒,从socket读取数据,处理客户请求,往epoll注册写就绪事件
5.主线程调用epoll_wait()等待socket可写
6.socket可写时,epoll_wait通知主线程。主线程将可写事件放入请求队列
7.工作线程被唤醒,往socket写入服务器处理结果
Proactor模式
将所有IO操作都交给主线程和内核处理,工作线程只负责业务逻辑
流程:
1.主线程调用aio_read向内核注册读完成事件,用信号捕捉
2.主线程处理其他逻辑
3.数据被读入用户缓冲区后,选择工作线程处理客户请求
4.请求处理完毕后,调用aio_write注册写完成事件,用信号捕捉
5.主线程处理其他逻辑
6.用户缓冲区数据写完毕之后,选择工作线程处理善后工作
Reactor与Proactor的区别主要在于 主线程与工作线程的分工
Reactor:主线程监听,工作线程处理IO以及业务逻辑
Proactor:将IO处理工作由主线程负责,IO操作完毕后,由工作线程处理业务逻辑