1.前提理论
这里研究的io模型都是针对网络io的 , 因为现在开发的应用程序基本上都是基于网络的
"""
Stevens在文章中一共比较了五种IO Model:
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用
* signal driven IO 信号驱动IO
* asynchronous IO 异步IO
由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。
"""
#1)等待数据准备 (Waiting for the data to be ready)
#2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
同步异步
阻塞非阻塞
常见的网络阻塞状态 :
accept
recv
recvfrom
send是主动触发的 , send的量很小 , 但是在内存copy到内存很快
send虽然它也有io行为但是不在我们的考虑范围
图解recv和send
2.阻塞IO(blocking IO)
我们之前写的都是阻塞io , 协程除外
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。
而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
回顾tcp服务端
import socket
sock = socket.socket()
sock.bind(("127.0.0.1", 8081))
sock.listen(1)
print('等待客户端连接.....')
conn, addr = sock.accept()
while 1:
msg = conn.recv(1024).decode()
print(msg)
if msg == "n": break
conn.send(msg.upper().encode())
conn.close()
sock.close()
# 在服务端开设多进程或者多线程 进程池线程池 其实还是没有解决工问题
该等的地方还是得等没有规避
只不过多个人等待的彼此互不干扰
3.非阻塞IO(non-blocking IO)
Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
代码实现
#服务端
from socket import *
import time
s=socket(AF_INET,SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
conn_l=[]
del_l=[]
while True:
try:
conn,addr=s.accept()
conn_l.append(conn)
except BlockingIOError:
print(conn_l)
for conn in conn_l:
try:
data=conn.recv(1024)
if not data:
del_l.append(conn)
continue
conn.send(data.upper())
except BlockingIOError:
pass
except ConnectionResetError:
del_l.append(conn)
for conn in del_l:
conn_l.remove(conn)
conn.close()
del_l=[]
#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8080))
while True:
msg=input('>>: ')
if not msg:continue
c.send(msg.encode('utf-8'))
data=c.recv(1024)
print(data.decode('utf-8'))
非阻塞IO实例
总结 :
"""
虽然非阻塞Io给你的感觉非常的牛逼
但是该模型会长时间占用CPU 并且不干活(一直在循环) 让CPU不停的空车
我们实际应用中也不会考虑使用非阻塞io模型
任何的技术点都有它存在的意
实际应用或者是思想借鉴
"""
4.IO多路复用(IO multiplexing)
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
"""
当监管的对象只有一个的时候其实io多路复用连阻塞io都比比不上!!
但是io多路复用可以一次性监管很多个对象
监管机制是操作系统本身就有的如果你想要用该监管机制( select )
需要你导入对应的select模块
server = socket.socket()
conn , addr = server.accept()
"""
#服务端
from socket import *
import select
s=socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('127.0.0.1',8081))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
read_l=[s,]
while True:
r_l,w_l,x_l=select.select(read_l,[],[])
print(r_l)
for ready_obj in r_l:
if ready_obj == s:
conn,addr=ready_obj.accept() #此时的ready_obj等于s
read_l.append(conn)
else:
try:
data=ready_obj.recv(1024) #此时的ready_obj等于conn
if not data:
ready_obj.close()
read_l.remove(ready_obj)
continue
ready_obj.send(data.upper())
except ConnectionResetError:
ready_obj.close()
read_l.remove(ready_obj)
#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8081))
while True:
msg=input('>>: ')
if not msg:continue
c.send(msg.encode('utf-8'))
data=c.recv(1024)
print(data.decode('utf-8'))
select网络IO模型
总结
"""
监管机制其实有很多
select机制 windows linux都有
poll机制 只在1inux有 poll 和 select都可以监管多个对象 但是po11监管的数量更多
上述 select和poll机制其实都不是很完美当监管的对象特别多的时候
可能会出现极其大的延时响应
epo11机制 只在1inux有
它给每一个监管对象都绑定一个回调机制
一旦有响应回调机制立刻发起提醒
针对不同的操作系统还需要考虑不同检測机制书写代码太多繁琐
有一个人能够根据你跑的平台的不同自动帮你选择对应的监管机制
selectors模块
"""
5.异步IO
Linux下的asynchronous IO其实用得不多,从内核2.6版本才开始引入。先看一下它的流程:
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
"""
异步io模型是所有模型中效率最高的也是使用最广泛的 感觉上面三个是一个理论推导 , 思想迭代的过程 , 最后到了这个异步io , 由于异步io是让操作系统帮我们做一些操作 , 我们通过原生的python代码是无法实现的 需要用一门能够直接和操作系统打交道的语言(c语言)处理 , 好在有人帮你封装好了模块和框架
相关的模块和框架
模块: async模块
异步框架: sanic tronado twisted (他们之所以快是因为内部使用的是异步io模型)
速度快! ! ! , 快到甚至可以开发游戏服务端
"""
async模块在爬虫领域中经常使用 , 后面会详细介绍怎么使用
6.IO模型比较
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。异步io nb