阻塞网络IO模型
什么是IO:就是输入输出
阻塞:遇到IO时停止等待输入或输出 才能进行后续逻辑
默认的IO模型都是阻塞IO模型
非阻塞网络IO模型
字面意思就是:在遇到IO时可以继续执行 不受影响
首先了解什么网络IO模型在什么阶段会阻塞
发送端:---发送数据(send)--会将数据从进程copy到缓存区 ,这时会阻塞(本地阻塞)
接收端:--等待接收数据(wait_data),这时会产生堵塞,发送端无数据发送过来就会阻塞---
接收到数据(sent_data),从缓存区copy到内存中去执行时 也会阻塞(本地阻塞)
因为本地阻塞 非常快 所以不必纠结 主要解决是的wait_data的问题
如何使用:
1 将server的blocking设置为False 即设置非阻塞
2 将客户端的 接收server.accept() client.recv() client.sent 分开处理 因为是这三个部分产生阻塞
服务器端 import socket import time server = socket.socket() server.bind(("127.0.0.1",1688)) server.listen() server.setblocking(False) # 默认为阻塞,True 设置为False 表示非阻塞 # 用来存储客户端的列表 clients = [] # 用来存储需要发送的数据和客户端对象 msgs = [] # 链接客户端的循环 while True: try: client,addr = server.accept() # 接受三次握手信息 这时是wait_data 会阻塞 # print("来了一个客户端了.... %s" % addr[1]) # 有人链接成功了 clients.append(client) # 添加到存储客户端列表中去 然后再去处理其他任务 就完成了非阻塞 # (因为这时有可能客户端只是连接过来,数据还没发过来,那么就可以去处理其他任务) except BlockingIOError as e: # print("还没有人连过来.....") time.sleep(0.01) # 收数据的操作 for c in clients[:]: # 从客户端出来接收数据 对for循环操作 要切片 防止出错 try: # 可能这个客户端还没有数据过来 # 开始通讯任务 data = c.recv(2048) if not data: c.close() clients.remove(c) #c.send(data.upper()) # 如果碰巧缓存区满了 这个数据就丢失了 # 由于此处捕获了异常 所以应该单独来处理发送数据 msgs.append((c,data)) except BlockingIOError as e: print("这个客户端还不需要处理.....",) except ConnectionResetError: # 断开后删除这个客户端 防止缓存区占满 c.close() clients.remove(c) # 发送数据的操作 for i in msgs[:]: try: c,msg = i c.send(msg.upper()) msgs.remove(i) # 如果发送成功! 删除这个数据 except BlockingIOError: pass
客户端 import socket client =socket.socket() client.connect(('127.0.0.1',1688)) while True: msg = input("msg:") if not msg:continue client.send(msg.encode('utf-8')) data=client.recv(2048).decode('utf-8') print(data)
非阻塞网络IO模型
带来的问题:但没有数据过来时,cpu也会一直在运行去询问是否有数据要处理 CPU这时就是处于一种无实际用途的占用
多路复用IO模型
为了解决上述问题的,那么就可以把这个工作交给别人去做(select) ,只需要去管理select就好.那么select解决的问题就是,记录接收的数据 和需要发送的数据 (存在rlist列表和wlist列表中),只要这些数据是非空有只时,那么就将这些数据交给CPU去处理,CPU只需要对其列表遍历找出有数据的就可以,当数据完成后就把多个列表中的数据删除掉
select监控多个socket
select的实现思路比较直接
1.先将所有socket放到一个列表中,
2.遍历这个列表将进程A 添加到每个socket的等待队列中 然后阻塞进程
3.当数据到达时,cpu执行中断程序将数据copy给socket 同时唤醒处于等待队列中的进程A
为了防止重复添加等待队列 还需要移除已经存在的进程A
4.进程A唤醒后 由于不清楚那个socket有数据,所以需要遍历一遍所有socket列表
从上面的过程中不难看出
1.select,需要遍历socket列表,频繁的对等待队列进行添加移除操作,
2.数据到达后还需要给遍历所有socket才能获知哪些socket有数据
两个操作消耗的时间随着要监控的socket的数量增加而大大增加,
服务器 import socket import select server = socket.socket() server.bind(("127.0.0.1",1688)) server.listen() """ 参数1 rlist 里面存储需要被检测是否可读(是否可以执行recv)的socket对象 参数2 wlist 里面存储需要被检测是否可写(是否可以执行send)的socket对象 参数3 xlist 存储你需要关注异常条件 忽略即可 参数4 timeout 检测超时时间 一段时间后还是没有可以被处理的socket 那就返回空列表 返回值: 三个列表 1 已经有数据到达的socket对象 2 可以发送数据的socket对象 怎么可以发 缓冲区没有满 3 忽略.... """ rlist = [server,] wlist = [] # 要发送的数据和socket msgs = [] while True: readable_list,writeable_list,_ = select.select(rlist,wlist,[]) # 阻塞直到socket可读或是可写 # 处理可读的socket for s in readable_list: # 其中可能是server的socket 可能是client的socket if s == server: client,addr = server.accept() rlist.append(client) else: # 不是server的那肯定就是client的socket对象 那就可以来接收数据 try: # 收数据 data = s.recv(2048) if not data: # 无数据会收空报错 加一个抛异常 raise ConnectionResetError() wlist.append(s) # s.send(data.upper()) # 将要发送的数据和socket 保存起来 msgs.append((s,data)) except ConnectionResetError: s.close() rlist.remove(s) if s in wlist:wlist.remove(s) # 处理可写的socket for s in writeable_list: for msg in msgs[:]: if msg[0] == s: s.send(msg[1].upper()) # 发送成功之后 删除已经无用的数据 并且需要将socket从wlist列表中删除 # 不删除会造成死循环 因为socket 一直处于可写状态 msgs.remove(msg) wlist.remove(s)
客户端 import socket client = socket.socket() client.connect(("127.0.0.1",1688)) while True: msg = input("msg:") if not msg:continue client.send(msg.encode('utf-8')) data=client.recv(2048) print(data.decode('utf-8'))
案例