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端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | # 非阻塞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端:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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模块,帮我们默认选择当前平台下最合适的;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #服务端 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) |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步