流:就是可以进行内核操作的对象。比如文件,socket,pipe
I/O操作:从流中读取数据,往流中写入数据。
阻塞模式:要执行的操作无法进行,一直等待条件满足时再执行。等待过程中,无操作,静静等待。(经济简单)
阻塞模式下,内核对于I/o事件的处理是阻塞或者唤醒。
非阻塞模式下,内核对于I/o事件交给其他对象处理,甚至直接忽略。
非阻塞忙轮询:等待过程中,一直查询执行条件是否满足,循环往复。直到条件满足再执行。(浪费时间精力)
缓冲区:目的是为了减少I/O操作引起的系统频繁调用(很慢!)当你操作一个流时,更多的是以缓冲区为单位进行操作。(用户和内核都需要缓冲区)
状态(管道事件) | 进程A(往管道的写入数据) | 管道(数据的存储,流动) | 进程B(从管道读取数据) |
---|---|---|---|
缓冲区(管道)空事件 | A 没有往管道写数据 | 管道是空的 | B无法读取数据,被阻塞着,就是B睡眠了。 |
缓冲区非空事件 | A开始给管道写数据 | 管道是非空的 | 内核通知B可以进行读取数据了。B解除阻塞,醒来 |
缓冲区满事件 | A阻塞, | 管道满了 | B没有读取数据 |
缓冲区非满事件 | A开始给管道写数据 | 管道非满了 | B进行了读取数据 |
阻塞模式缺点:一个线程只能处理一个I/o模式。如果要同时处理多个流,要么多进程,要么多线程。但是这两个方法效率都不高。
非阻塞忙轮询:不停地把所有流从头到尾问一遍,又从头开始,这样就可以处理多个流了。
缺点是:如果所有的流中都没有数据,只会白白浪费CPU,
非阻塞无差别轮询:引进一个代理。代理同时观察许多流的I/O事件,在空闲的时候,会把当前的线程阻塞掉,当有一个或者多个流有I/o操作事件时,就唤醒线程,于是就轮询一遍所有的流。
没有I/o事件时,程序阻塞到代理select处,我们仅仅从select处知道了有I/O事件发生了,但是不知道是哪个流,只能无差别轮询。当流多的时候,效率也是相当的低了。
非阻塞事件轮询:代理eventpool会将哪个流发生了什么I/O事件通知我们。此时对这些流的操作都是有意思的,复杂度降到了n(1)
epoll是对select,poll模型的改进,提高了网络编程的性能,广泛用于大规模并发请求的c/s结构中。
1 触发方式:
边缘触发/水平触发,(只使用unix和linux操作系统)
2 原理
3 步骤
1 创建一个epoll对象
2 告诉epoll对象,在指定的socket上监听指定的事件
3 询问epoll对象,从上次查询以来,那些socket发生了那些指定的事件
4 在这些socket上执行一些操作,
5 告诉epoll对象,修改socket列表和或者事件,并监控
6 重复 3 -5 ,直到完成
7 销毁epoll对象
4 相关用法:
1 导入select模块
import select
2 创建epoll对象
epoll=select.epoll()
3 注册要监控的文件句柄和事件
epoll.register(文件句柄,事件类型)
事件类型:
select.EPOLLIN 可读事件
select.EPOLLOUT 可写事件
select.EPOLLERR 错误事件
select.EPOLLHUP 客户端断开事件
4 epoll.unregister(文件句柄) 销毁文件句柄
5 epoll.fileno() 返回epoll的控制文件描述符
6 epoll.modify(fileno,event) fileno 是文件描述符,event是事件类型,作用是修改文件描述符,所对应的事件
7 epoll.fromfd(fileno) 从一个文件描述符,创建一个epoll对象
8 epoll.close() 关闭epoll对象的控制文件描述符