IO模型
模型即解决某个问题的固定套路或方法.
I/O指的是输入/输出,输入或输出数据需要很长一段时间(相对于CPU而言),在等待输入/输出的过程中,CPU处于闲置状态,造成资源浪费.
注意:IO类型较多,例如socket的网络IO,等待键盘的输入等,对比起来socket的网络IO需要等待的时间是最长的(解决IO的主要问题).
1. socket网络IO步骤和过程:
操作系统的两种状态:内核态(权限极高)和用户态,当操作系统需要控制硬件时,例如接受网卡数据,先切换到内核态
-
wait_data:等待数据到达网卡
buffer(缓冲):数据读入内存占用的空间,降低IO次数
cache(缓存):内存读取数据占用的空间,提高读取效率
-
copy_data:从内核copy到应用程序缓冲区
recv(),accept()需要经历wait和copy阶段,
而send()只经过copy
2.阻塞IO模型
默认情况下 你写出TCP程序就是阻塞IO模型
该模型 提高效率方式,当你执行recv/accept 会进入wait_data的阶段,
1.你的进程会主动调用一个block指令,进程进入阻塞状态,同时让出CPU的执行权,操作系统就会将CPU分配给其它的任务,从而提高了CPU的利用率
2.当数据到达时,首先会从内核将数据copy到应用程序缓冲区,并且socket将唤醒处于自身的等待队列中的所有进程
之前使用多线程 多进程 完成的并发 其实都是阻塞IO模型 每个线程在执行recv时,也会卡住
3.非阻塞IO模型
在调用recv()/accept()时不会阻塞
使用方法:1. server.setblocking(false)
该模型在没有数据到达时,会跑出异常,我们需要捕获异常,然后继续不断地询问系统内核直到,数据到达为止
可以看出,该模型会大量的占用CPU资源做一些无效的循环, 效率低于阻塞IO
4.多路复用IO模型
属于事件驱动模型
多个socket使用同一套处理逻辑
如果将非阻塞IO 比喻是点餐的话,相当于你每次去前台,照着菜单挨个问个遍
多路复用,直接为前台那些菜做好了,前台会给你返回一个列表,里面就是已经做好的菜
对比阻塞或非阻塞模型,增加了一个select,来帮我们检测socket的状态,从而避免了我们自己检测socket带来的开销
select会把已经就绪的放入列表中,我们需要遍历列表,分别处理读写即可
import socket
import time
import select
server = socket.socket()
# server.setblocking(False) #在多路复用中,阻塞出现在select()
server.bind(('127.0.0.1',1111))
server.listen(5)
#待检测是否可读的列表
r_list = [server]
#待检测是否可写的列表
w_list = []
#待发送的数据
msgs = {}
print('开始检测!')
while True:
read_ables,write_ables,_= select.select(r_list,w_list,[])
# print('检测结果')
print(read_ables) #可以接受请求了
print(write_ables) #可以发送响应了
#处理可读(接受数据)
for obj in read_ables: #拿出所有可读数据的socket
#(服务器or客户端)
if obj == server: #服务器
c,addr = server.accept()
r_list.append(c) #客户端交给select检测
else: #如果是客户端,接受数据
try: #处理客户端异常关闭
data = obj.recv(1024)
if not data:raise ConnectionResetError
#将要发送数据的socket加入到列表中让select检测
w_list.append(obj)
#将socket对象及发送的data数据放到临时的字典中{socket:[data,data1]}
if obj in msgs:
msgs[obj].append(data)
else:
msgs[obj] = [data]
except ConnectionResetError:
obj.close()
r_list.remove(obj) #检测列表中清除socke
#处理可写(发送响应数据)
for obj in write_ables:
msg_list = msgs.get(obj)
if msg_list:
#遍历发送所有数据
for m in msg_list:
try:
obj.send(m.upper())
except ConnectionResetError:
obj.close()
w_list.remove(obj)
break
#容器清除数据
msgs.pop(obj)
#检测可写列表中清除socket
w_list.remove(obj)
多路复用对比非阻塞 ,多路复用可以极大降低CPU的占用率
注意:多路复用并不完美 因为本质上多个任务之间是串行的,如果某个任务耗时较长将导致其他的任务不能立即执行,多路复用最大的优势就是高并发,数据传输量小