IO模型
数据传输过程中经历的两个阶段:
send:是将数据从应用程序内存copy到操作系统缓存,这个过程称之为copydata
服务器要接受数据:recv 是从操作系统缓冲区copy数据到应用程序内存
如果数据已经到达缓存区则直接copy 如果数据还没有到达会进入阻塞状态一直等待有数据发过来 等待的过程称之为 waitdata
accept 三次握手 也会经历copy wait 阶段
recvfrom wait完后copy
sendto copy
sendall copy
IO模型
模型即套路 是解决某个固定问题的方式方法
IO模型即解决IO问题的方式方法
IO指的输入输出,输入输出设备的速度 对比CPU而言是非常慢的,比如recv input等都是IO操作
IO操作的最大问题就是会阻塞程序执行
IO模型要解决的也仅仅是网络IO操作
IO模型有以下几个
1.阻塞IO
socket默认就是阻塞的
问题:同一时间只能服务一个客户端
方法1:多线程
优点:如果并发量不高 ,效率是较高的 因为每一个客户端都有单独的线程来处理
弊端:不可能无限的开启线程 线程也需要占用资源
方式2:多进程
优点:可以多个CPU并行处理
弊端:占用资源非常大 一旦客户端稍微多一点 立马就变慢了
线程池:
优点:保证了服务器正常稳定运行,还帮你负责创建和销毁线程以及任务分配
弊端:一旦并发量超出最大线程数量,就只能等前面的运行完毕
进程池:
真正导致效率低的是阻塞问题 但是上述几个方法 并没有真正解决阻塞问题 仅仅是避开了阻塞问题
协程:
基于单线程并发
优点:不需要创建一堆线程,也不需要在线程间做切换
弊端:不能利用多核优势 单核处理器 性能也是由上限的 如果真的并发特别大 name处理速度回变慢
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket,os c=socket.socket() c.connect(('127.0.0.1',1688)) while True: msg='%s 发来问候!'%os.getpid() if not msg:continue c.send(msg.encode()) data=c.recv(1024) print(data.decode())
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket from threading import Thread s=socket.socket() s.bind(('127.0.0.1',1688)) s.listen() print('等待连接') def talking(c): while True: try: data=c.recv(1024) print(data.decode()) if not data: c.close() break c.send(data.upper()) except ConnectionResetError: c.close() break while True: c,addr=s.accept() print('连接成功') t=Thread(target=talking,args=(c,)) t.start()
2.非阻塞IO
即遇到IO操作也不导致程序阻塞,会继续执行 意味着即使遇到IO操作 CPU执行权也不会被剥夺 程序效率就变高了
以下程序 占用CPU太高
原因是 需要无线的循环 去向操作系统拿数据
setblocking(False) 设置socket是否阻塞 默认为True
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket c=socket.socket() c.connect(('127.0.0.1',1688)) print('connecting..') while True: msg=input('>>>:') if not msg:continue c.send(msg.encode()) data=c.recv(1024) print(data.decode())
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket,time s=socket.socket() s.bind(('127.0.0.1',1688)) s.listen() #设置socket 是否阻塞 默认为True s.setblocking(False) #所有的客户端socket cs=[] #所有需要返回数据的客户端 send_cs=[] while True: try: c,addr=s.accept() #三次握手 print('连接成功') cs.append(c)#存储已经连接成功的客户端 except BlockingIOError: # 没有数据准备 可以作别的事情 # print("收数据") for i in cs[:]: try: data=i.recv(1024) if not data: i.close() cs.remove(i) print(data.decode()) # 把数据和连接放进去 send_cs.append((i,data)) # c.send(data.upper()) # io # send也是io操作 在一些极端情况下 例如系统缓存满了 放不进去 那肯定抛出 # 非阻塞异常 这时候必须把发送数据 单独拿出来处理 因为recv和send都有可能抛出相同异常 # 就无法判断如何处理 except BlockingIOError: continue except ConnectionResetError: i.close() # 从所有客户端列表中删除这个连接 cs.remove(i) for item in send_cs[:]: c,data=item try: c.send(data.upper()) # 如果发送成功就把数据从列表中删除 send_cs.remove(item) except BlockingIOError: # 如果缓冲区慢了 那就下次再发 continue except ConnectionResetError: c.close()# 关闭连接 send_cs.remove(item)# 删除数据 # 从所有客户端中删除这个已经断开的连接 cs.remove(c)
3.IO多路复用 *******
也是单线程并发处理所有请求
与非阻塞不同之处在于 不需要频繁不断发送系统调用
只要等待select 选择出准备就绪socket然后进行处理即可
如果说 没有任何一个socket 准备就绪 select就会阻塞住 这意味着效率降低了吗?
并不是 服务器的工作就是处理一堆socket 既然没有socket需要处理 就不需要做任何操作了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket c = socket.socket() c.connect(("127.0.0.1", 1688)) print("connect....") while True: msg = input(">>>:").strip() if not msg:continue c.send(msg.encode("utf-8")) data = c.recv(1024) print(data.decode("utf-8"))
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
""" IO多路复用 用一个线程来并发处理所有的客户端 原本我们是直接问操作系统 要数据, 如果是阻塞IO 没有数据就进入阻塞状态 非阻塞IO 没有数据就抛出异常 然后继续询问操作系统 在多路复用模型中,要先问select 哪些socket已经准备就绪 然后在处理这些已经就绪的socket 既然是已经就绪 那么执行recv或是send 就不会在阻塞 select模块只有一个函数就是select 参数1:r_list 需要被select检测是否是可读的客户端 把所有socket放到该列表中,select会负责从中找出可以读取数据的socket 参数2:w_lirt 需要被select检测是否是可写的客户端 把所有socket放到该列表中,select会负责从中找出可以写入数据的socket 参数3:x_list 存储要检测异常条件 ....忽略即可 返回一个元组 包含三个列表 readables 已经处于可读状态的socket 即数据已经到达缓冲区 writeables 已经处于可写状态的socket 即缓冲区没满 可以发送... x_list:忽略 从可读或写列表中拿出所有的socket 依次处理它们即可 """ import socket import select s=socket.socket() s.bind(('127.0.0.1',1688)) s.listen() # 在多路复用中 一旦select交给你一个socket 一定意味着 该socket已经准备就绪 可读或是可写 # s.setblocking(False) r_list=[s] w_list=[] # 存储需要发送的数据 已及对应的socket 把socket作为key 数据作为value data_dic={} while True: readables,writeables,_=select.select(r_list,w_list,[]) # 接收数据 以及服务器建立连接 for i in readables: if i==s:# 如果是服务器 就执行accept c,_=i.accept() r_list.append(c) else:# 是一个客户端端 那就recv收数据 try: data=i.recv(1024) if not data:#linux 对方强行下线或是 windows正常下线 i.close() r_list.remove(i) continue print(data) # 发送数据 不清楚 目前是不是可以发 所以交给select来检测 w_list.append(i) data_dic[i]=data# 把要发送的数据先存在 等select告诉你这个连接可以发送时再发送 except ConnectionResetError:# windows强行下线 i.close() r_list.remove(i)# 从检测列表中删除 # 发送数据 for i in writeables: try: i.send(data_dic[i].upper())# 返回数据 # data_dic.pop(i) # w_list.remove(i) except ConnectionResetError: i.close() finally: data_dic.pop(i)# 删除已经发送成功的数 w_list.remove(i)# 从检测列表中删除这个连接 如果不删除 将一直处于可写状态
4.异步IO 爬虫阶段讲
5.信号驱动 了解
迭代过程中 不允许修改元素
# li = [1,2,3,4,5] # # rm_list = [] # # for i in li: # rm_list.append(i) # # for i in rm_list: # li.remove(i) # # print(li) li=[1,2,3,4,5] # print(id(li[:])) # print(id(li)) # 用切片的方式 产生一个新的列表 新列表中元素与就列表完全相同 # 遍历新列表 删除就列表 for i in li[:]: li.remove(i) print(li)