IO模型介绍:
主进程的阻塞问题 ,多进程 多线程 只是分离了阻塞,而没有避免IO,因此有以下的模型:
1.IO阻塞模型(blocking IO);
2.非IO阻塞模型(nonblocking IO);
3.IO多路复用(IO multiplexing);
4.异步IO(asynchronous IO);用Python实现不了,是操作系统帮你的。
5.信号驱动IO(signal driven IO )不常用;
IO阻塞模型的流程图:
即就是平时我们所写的socket,accpet,recv,都需要等待,阻塞。
2非IO阻塞模型的流程图:
就是不断的去循环 询问操作系统是否有顺序;
优点:CUP的利用率提高了;缺点:增加了CUP的负担;不推荐使用;
基于非IO阻塞的socket
server端:
# 非阻塞IO 实际上就是在得到连接后就 循环的向操作系统询问 是否有数据,没有就可以做其他的事情(在这里实际上就是接收到其他的连接了,后
# 循环的向操作系统询问 是否有数据)直达操作系统copy一份数据给进程。如果对面在传就在这样的接收,这样的过程就避免了IO阻塞等过程的
# 时间,
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.setblocking(False) #IO阻塞变为不阻塞了。
sk.listen()
conn_lsit = []
while True:
try:
conn,addr = sk.accept() #有连接就接收到,
conn_lsit.append(conn) #为了防止如果有很多的连接的话,只能拿到最后一个,因为其他的被覆盖掉了。
except BlockingIOError: #没有连接来的时候报错
del_list=[]
for conn in conn_lsit:
try:
ret = conn.recv(1024).decode('utf-8') #尝试着接收数据,没有就继续向下执行
#当客户端连接关闭后,服务端就会接收空字符
if not ret: #证明此连接传输完成,需要关闭此连接的服务端。
conn.close()
del_list.append(conn) #这个连接已经接收到了数据。
else:
print(ret) #正常的传输
msg = input('>>>')
conn.send(msg.encode('utf-8')) #发送数据是自己发送的不会调用操作系统,因此不会阻塞。
except BlockingIOError: #没有数据的时候报错
pass
if del_list:
for conn in del_list:
conn_lsit.remove(conn) #为了在以后的循环中不再拿到已经关闭的连接
# conn.send(ret.upper())
#
# conn.close()
# sk.close()
client端:
import threading
import socket
def func():
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
sk.send(b'hello world')
ret = sk.recv(1024)
print(ret)
sk.close()
for i in range(10):
threading.Thread(target=func).start() #开启10个线程。
3.IO多路复用的流程图:
用select模块来监听recv和accept,因此阻塞就变成了select;有数据的时候就通知;对于单个的对象所用的时间比IO阻塞要多,但是多个对象的时候效率就高了;
优点:减少了CUP的负担,同时也能接收到数据,增加了CUP的利用率,一般多用这个模型;
server端:
import socket
import select
sk = socket.socket()
sk.bind(('127.0.0.1',8099))
sk.listen()
read_lst = [sk]
while True:
rl,wl,xl = select.select(read_lst,[],[]) # select阻塞,rl可以读的 wl可以写的 xl可以改的 [sk,conn],
# 当sk连接和conn连接同时有数据发来时 rl一个列表里面同时有两者
# rl = [sk,coon]
# 有数据的时候就会响应相应的sk或者是conn
for item in rl:
if item == sk:
conn,addr = item.accept() # 有数据等待着它接收
read_lst.append(conn)
else:
ret = item.recv(1024).decode('utf-8')
if not ret: #在客户端关闭的时候会发送一个空字符
item.close()
read_lst.remove(item)
else:
print(ret)
item.send(('received %s'%ret).encode('utf-8'))
client端:
import time
import socket
import threading
def client_async(args):
sk = socket.socket()
sk.connect(('127.0.0.1',8099))
for i in range(10):
time.sleep(2)
sk.send(('%s[%s] :hello'%(args,i)).encode('utf-8'))
print(sk.recv(1024))
sk.close()
for i in range(10):
threading.Thread(target=client_async,args=('*'*i,)).start()
4.异步IO的流程图:
操作系统帮你做数据准备阶段和数据copy阶段;用户在这期间可以做别的,操作系统拿到数据后就直接给你了。
5中IO模型的比较图:
从图中可以看出IO阻塞;非IO阻塞;以及IO多路复用 都避免不了数据的copy时间,而异步IO可以。
readlst [sk,conn,conn2,conn3] 100 问一百次
select ;poll 随着要检测的数据增加 效率会下降
select 有数目的限制;
poll 能处理的对象更多;
epoll 能处理多对象 不是使用轮换的询问; 而是拿到数据后就直接调用回调函数 。
epoll —— 但是只能在 linux执行,而epoll在windows下就不支持,好在我们有selectors模块,帮我们默认选择当前平台下最合适的;
#服务端
from socket import *
import selectors
sel=selectors.DefaultSelector() # 创建一个默认的多路复用模型
def accept(sk):
conn,addr=sk.accept()
sel.register(conn,selectors.EVENT_READ,read)
def read(conn):
try:
data=conn.recv(1024)
if not data: #win8 win10
print('closing',conn)
sel.unregister(conn)
conn.close()
return
conn.send(data.upper()+b'_SB')
except Exception: # linux操作系统
print('closing', conn)
sel.unregister(conn)
conn.close()
sk=socket(AF_INET,SOCK_STREAM)
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8088))
sk.listen(5)
sk.setblocking(False) #设置socket的接口为非阻塞
sel.register(sk,selectors.EVENT_READ,accept) #相当于往select的读列表里append了一个文件句柄server_fileobj,并且绑定了一个回调函数accept
while True:
events=sel.select() #检测所有的fileobj,是否有完成wait data的 #[sk,conn]
for sel_obj,mask in events: # 有人触动了你在sel当中注册的对象
callback=sel_obj.data #callback=accpet # sel_obj.data就能拿到当初注册的时候写的accept/read方法
callback(sel_obj.fileobj) #accpet(sk)/read(conn)