模型即解决某个问题的固定套路html
I/O 指的是输入输出python
IO的问题: 当咱们要输入数据或是输出数据一般须要很长一段时间,固然是对于CPU而言linux
在等待输入的过程当中,CPU就处于闲置状态 没事干! 形成了资源浪费服务器
注意: IO其实有不少类型,例如,socket网络IO,内存到内存的copy,等待键盘输入,对比起来socket网络IO须要等待的时间是最长的,这也是我们重点关注的地方,网络
学习IO模型要干什么? 就是在等待IO操做的过程当中利用CPU,作别的事情多线程
操做系统有两种状态:内核态 和 用户态 , 当操做系统须要控制硬件时,例如接收网卡上的数据,必须先转换到内核态,接收完数据后,要把数据从操做系统缓冲区,copy到应用程序的缓冲区,从内核态转为用户态;并发
涉及到的步骤app
1.wait_data异步
2.copy_datasocket
recv accept 须要经历 wait -> copy
send 只须要经历copy
默认状况下 你写出TCP程序就是阻塞IO模型
该模型 提升效率方式,当你执行recv/accept 会进入wait_data的阶段,
1.你的进程会主动调用一个block指令,进程进入阻塞状态,同时让出CPU的执行权,操做系统就会将CPU分配给其它的任务,从而提升了CPU的利用率
2.当数据到达时,首先会从内核将数据copy到应用程序缓冲区,而且socket将唤醒处于自身的等待队列中的全部进程
注意:以前使用多线程 多进程 完成的并发 其实都是阻塞IO模型 每一个线程在执行recv时,也会卡住
非阻塞IO模型与阻塞模型相反 ,在调用recv/accept 时都不会阻塞当前线程
使用方法: 将本来阻塞的socket 设置为非阻塞
该模型在没有数据到达时,会抛出异常,咱们须要捕获异常,而后继续不断地询问系统内核直到数据到达为止
案例:
import socket import time s = socket.socket() s.bind(("127.0.0.1",1688)) # 设置为非阻塞 模型 s.setblocking(False) #False表示不阻塞 s.listen() # 保存全部的客户端socket cs = [] # 用于保存须要发送的数据 msgs = [] while True: try: c,addr = s.accept() # 完成三次握手 print("来客了, xxx接客了!") cs.append(c) except BlockingIOError: print("尚未人来/. 好寂寞啊!") # 代码执行到这里则说明 没有链接须要处理 # 就能够处理收发数据的任务 for c in cs[:]: # 专门处理接收数据 try: data = c.recv(1024) if not data: raise ConnectionResetError msgs.append((c,data.upper())) #c.send(data.upper()) # 不能直接发 当内核缓冲区满了 就会抛出异常 致使数据发不出去了 except BlockingIOError: print("客官 你却是说句话啊!") pass except ConnectionResetError: c.close() cs.remove(c) # 处理发送数据 for i in msgs[:]: try: i[0].send(i[1]) msgs.remove(i) except BlockingIOError: pass except ConnectionResetError: #关闭链接 i[0].close() # 删除数据 msgs.remove(i) # 删除链接 cs.remove(i[0])
客户端
import socket c = socket.socket() c.connect(("127.0.0.1",1688)) while True: msg = input("").strip() if not msg:continue c.send(msg.encode("utf-8")) print(c.recv(1024).decode('utf-8'))
能够看出,该模型会大量的占用CPU资源作一些无效的循环, 效率低于阻塞IO
属于事件驱动模型
多个socket使用同一套处理逻辑
若是将非阻塞IO 比喻是点餐的话,至关于你每次去前台,照着菜单挨个问个遍
而多路复用,至关于直接问前台那些菜作好了,前台会给你返回一个列表,里面就是已经作好的菜
对比阻塞或非阻塞模型,增长了一个select,来帮咱们检测socket的状态,从而避免了咱们本身检测socket带来的开销
select会把已经就绪的socket放入列表中,咱们须要遍历列表,分别处理读写便可
多路复用中select的职责:
案例:
import socket import time import select s = socket.socket() s.bind(("127.0.0.1",1688)) # 设置为非阻塞 模型 s.setblocking(True) #在多路复用中 阻塞与非阻塞没有区别 由于select会阻塞直到有数据到达为止 s.listen() # 待检测是否可读的列表 r_list = [s] # 待检测是否可写的列表 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 #有多是服务器 有多是客户端 if s == obj: # 服务器 print("来了一个客户端 要链接") client,addr = s.accept() r_list.append(client) # 新的客户端也交给select检测了 else:# 若是是客户端则执行recv 接收数据 print("客户端发来一个数据") try: data = obj.recv(1024) if not data:raise ConnectionResetError print("有个客户端说:",data) # 将要发送数据的socket加入到列表中让select检测 w_list.append(obj) # 将要发送的数据已经socket对象丢到容器中 if obj in msgs: # 因为容器是一个列表 因此须要先判断是否已经存在了列表 msgs[obj].append(data) else: msgs[obj] = [data] except ConnectionResetError: obj.close() r_list.remove(obj) # 处理可写的 也就是send发送数据 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中删除 w_list.remove(obj)
客户端
import socket c = socket.socket() c.connect(("127.0.0.1",1688)) while True: msg = input("").strip() if not msg:continue c.send(msg.encode("utf-8")) print(c.recv(1024).decode('utf-8'))
多路复用对比非阻塞 ,多路复用能够极大下降CPU的占用率
http://www.javashuo.com/article/p-wfbzikdm-dc.html
非阻塞IO不等于异步IO 由于copy的过程是一个同步任务 会卡主当前线程
而异步IO 是发起任务后 就能够继续执行其它任务,当数据copy到应用程序缓冲区完成后,才会给你的线程发送信号 或者执行回调
asyncio python3.4 出现 目前不少重要的第三方模块都不支持该模块,因此没有普遍使用,其内部也是使用了生成器!
简单的说就是 当某个事情发生后 会给你的线程发送一个信号,你的线程就能够去处理这个任务
不经常使用,缘由是 socket的信号太多,处理起来很是繁琐