一.先讲一个形象比喻五种io模型
1.阻塞I/O模型
老李去火车站买票,排队三天买到一张退票。
耗费:在火车站吃喝拉撒3天,其他事一件没干。
2.非阻塞I/O模型
老李去火车站买票,隔12小时去火车站文有没有退票,三天后买到一张票。
耗费:往返车站6次,路上6小时,其他时间做了好多事。
3.I/O
1)select/poll
老李去火车站买票,委托黄牛,(select黄牛一次只能帮1024个人买票,poll黄牛无上限)然后每隔离6小时打电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100,打电话17次
2)epoll
老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
耗费:往返车站两次,路上2小时,黄牛手续费100元,无需打电话。
4)信号驱动I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,免费黄牛100元,无需打电话。
5)异步I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
耗费:往返车站1次,路上一小时,免黄牛费100,无需打电话。
二.waitdata 和 copydata
1)等待数据准备 (Waiting for the data to be ready) 2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。
补充
#1、输入操作:read、readv、recv、recvfrom、recvmsg共5个函数,如果会阻塞状态,则会经理wait data和copy data两个阶段,如果设置为非阻塞则在wait 不到data时抛出异常 #2、输出操作:write、writev、send、sendto、sendmsg共5个函数,在发送缓存区满了会阻塞在原地,如果设置为非阻塞,则会抛出异常 #3、接收外来链接:accept,与输入操作类似 #4、发起外出链接:connect,与输出操作类似
三.阻塞IO模型
#服务端 from concurrent.futures import ThreadPoolExecutor import socket server = socket.socket() #重用端口 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(("127.0.0.1",9999)) server.listen(5) #线程池 pool=ThreadPoolExecutor(3) def data_handler(conn): print("一个新连接...") while True: data = conn.recv(1024) conn.send(data.upper()) while True: conn,addr = server.accept() #切到处理数据的任务去执行 pool.submit(data_handler,conn) #客户端 import socket c = socket.socket() c.connect(("127.0.0.1",9999)) while True: msg = input(">>>:") if not msg:continue c.send(msg.encode("utf-8")) data = c.recv(1024) print(data.decode("utf-8"))
四.非阻塞IO模型
在非阻塞IO中 需要不断循环询问操作是否有需要处理的数据
这一来 对应程序而言 效率确实高
但是操作系统而言 你的程序就像一个病毒 CPU江被你强行霸占
当你的TCP程序 没有锁 没有数据接收 没有数据发送时 就是在做无用循环 浪费系统资源
#服务器 from concurrent.futures import ThreadPoolExecutor import socket server = socket.socket() #重用端口 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(("192.168.11.96",9998)) server.listen(5) #设置是否为阻塞 默认阻塞 server.setblocking(False) def data_handler(conn): print("一个新连接..") while True: data = conn.recv(1024) conn.send(data.upper()) #已经连接的客户端 clients = [] # 需要发送的数据 send_datas=[] #已经发送完的 需要删除的数据 del_datas = [] # 待关闭的客户端 closed_cs = [] import time while True: try: conn,addr = server.accept() #切到处理数据的任务去执行 #代码走到这里才算是连接成功 #把连接成功的客户端存起来 clients.append(conn) except BlockingIOError: # print("没有可以处理的连接 就干别的活儿") #要处理的是已经连接成功的客户端 # 接受数据 for c in clients: try: data = c.recv(1024) if not data: #对方关闭了连接 c.close() #从客户端列表中删除它 closed_cs.append(c) continue print("收到%s"%data.decode("utf-8")) #现在非阻塞 send直接往缓存塞 如果缓存满了 肯定有错误 需要单独处理 #c.send(data.upper()) send_datas.append((c,data)) except BlockingIOError: pass except ConnectionRefusedError: # 对方关闭了连接 c.close() #从客户端iebook中删除它 closed_cs.append(c) #处理发送数据 for data in send_datas: try: data[0].send(data[1].upper()) #发送成功需要删除 不能直接删除 # send_datas.remove(data) del_datas.append(data) except BlockingIOError: continue except ConnectionRefusedError: # 客户端连接需要删除 data[0].close() closed_cs.append(data[0]) #等待发送的数据需要删除 del_datas.append(data) # 删除无用数据 for d in del_datas: # 从待发送的列表中删除 send_datas.remove(d) del_datas.clear() for c in closed_cs: clients.remove(c) closed_cs.clear() #客户端 from concurrent.futures import ThreadPoolExecutor import socket server = socket.socket() #重用端口 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(("192.168.11.96",9998)) server.listen(5) #设置是否为阻塞 默认阻塞 server.setblocking(False) def data_handler(conn): print("一个新连接..") while True: data = conn.recv(1024) conn.send(data.upper()) #已经连接的客户端 clients = [] # 需要发送的数据 send_datas=[] #已经发送完的 需要删除的数据 del_datas = [] # 待关闭的客户端 closed_cs = [] import time while True: try: conn,addr = server.accept() #切到处理数据的任务去执行 #代码走到这里才算是连接成功 #把连接成功的客户端存起来 clients.append(conn) except BlockingIOError: # print("没有可以处理的连接 就干别的活儿") #要处理的是已经连接成功的客户端 # 接受数据 for c in clients: try: data = c.recv(1024) if not data: #对方关闭了连接 c.close() #从客户端列表中删除它 closed_cs.append(c) continue print("收到%s"%data.decode("utf-8")) #现在非阻塞 send直接往缓存塞 如果缓存满了 肯定有错误 需要单独处理 #c.send(data.upper()) send_datas.append((c,data)) except BlockingIOError: pass except ConnectionRefusedError: # 对方关闭了连接 c.close() #从客户端iebook中删除它 closed_cs.append(c) #处理发送数据 for data in send_datas: try: data[0].send(data[1].upper()) #发送成功需要删除 不能直接删除 # send_datas.remove(data) del_datas.append(data) except BlockingIOError: continue except ConnectionRefusedError: # 客户端连接需要删除 data[0].close() closed_cs.append(data[0]) #等待发送的数据需要删除 del_datas.append(data) # 删除无用数据 for d in del_datas: # 从待发送的列表中删除 send_datas.remove(d) del_datas.clear() for c in closed_cs: clients.remove(c) closed_cs.clear()
五.异步IO模型
import asyncio asyncio.coroutine() from concurrent.futures import ThreadPoolExecutor def task(): print("read start") with open(r"D:python视频存放目录上海python全栈4期day40多路复用,降低CPU占用服务器.py",encoding="utf-8") as f: text = f.read() # f.write() print("read end") return text def fin(f): print("fin") print(f.result()) pool = ThreadPoolExecutor(1) future = pool.submit(task) future.add_done_callback(fin) print("主 over") # 这种方式看起来像是异步IO 但是对于子线程而言不是 # 在子线程中 执行read 是阻塞的 以为CPU必须切走 但是不能保证切到当前程序的其他线程 # 想要的效果就是 在执行read 是不阻塞 还能干其他活 谁能实现 只有协程 # asyncio 内部是使用的是协程
六.IO模型比较分析