035_IO 模型
IO 模型分类
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用
* signal driven IO 信号驱动IO
* asynchronous IO 异步IO
1,阻塞IO 的图
recv数据
阻塞IO
非阻塞IO
IO多路复用
2,
阻塞IO : 工作效率低
非阻塞IO : 工作效率高,频繁询问切换加大CPU的负担
IO多路复用: 在有多个对象需要IO阻塞的时候,能够有效的减少阻塞带来的时间损耗,且能够在一定程度上减少CPU的负担
异步IO : asyncio 异步IO 工作效率高 CPU的负担少
3,网络IO
# recv recvfrom accept requests.get()
# send connect sendto
# send connect sendto
4,IO的两个阶 段
# 数据准备阶段
# 数据copy阶段
# 数据copy阶段
解决阻塞IO 问题
1)多进程 多线程 分离了阻塞
但进程线程不能无限开!
2)以后用进程都用进程池
单纯的进程池不能满足用户的需求,只适合小并发的问题,真正需要我们解决的是I/O问题
但进程线程不能无限开!
2)以后用进程都用进程池
单纯的进程池不能满足用户的需求,只适合小并发的问题,真正需要我们解决的是I/O问题
1,非阻塞IO实例
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
sk.setblocking(False)
# 异步的,但是来不及打开client端建立连接,就已经报错
# 因为下一句不阻塞了
# recv 也不阻塞了,但一样会报错 ,有连接时,且设为不阻塞时,没消息就打印空消息。
# 原因:调用阻塞异常错误(变量没有被赋值)
conn,addr = sk.accept()
print(conn)
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
sk.setblocking(False)
conn_lst = [] # 用来保存已经建立的连接
while True:
try:
conn,addr = sk.accept() #非阻塞
conn_lst.append(conn) # 不将建立成功的连接用列表保存下来,conn会不断被新连接覆盖。
except BlockingIOError:
del_lst = []
for c in conn_lst:
try:
msg = c.recv(10).decode('utf-8') # recv不会阻塞
if not msg:
c.close()
del_lst.append(c)
else:
print(msg)
c.send(msg.upper().encode('utf-8'))
except BlockingIOError:
pass
if del_lst:
for del_item in del_lst:
conn_lst.remove(del_item)
import time
import socket
import threading
def func():
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
time.sleep(1)
sk.send(b'hi')
print(sk.recv(10))
sk.close()
for i in range(10):
threading.Thread(target=func,).start()
多路复用
# 操作系统提供的多路复用机制
# select 、 poll 、 epoll、
# windows 上只有 select
# linux 兼容三种
# 原来是recv,现在是交给select ;select([recv])
1,参数
select(rlist,wlisr,xlist,timeout = None)
等能 读,写,改 三个参数是列表,必须传参,没有参数传空列表。返回的结果是一个有三个列表的元组,分别是读,写,改的结果。
2,实例
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()
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]
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'))
3,
# select poll 随着要检测的数据增加 效率会下降,不适合处理大并发。
# select 有数目的限制
# poll 能处理的对象更多
# epoll 能处理多对象且 不是使用轮询 而是用回调函数 —— linux
# poll 能处理的对象更多
# epoll 能处理多对象且 不是使用轮询 而是用回调函数 —— linux
#这三种IO多路复用模型在不同的平台有着不同的支持,而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)