多路复用 阻塞/非阻塞IO模型 网络IO两个阶段

1.网络IO的两个阶段 waitdata copydata
send 先经历:copydata阶段
recv 先经历:waitdata阶段 再经历 copydata阶段


2.阻塞的IO模型
之前写的都是阻塞 无论多线程 多进程 还是进程池 线程池


3.非阻塞IO模型
非阻塞:最直接的体现 所有和读写相关的函数 都不会阻塞
意味着 在读写的时候 并不能确定目前是否可以读写 一旦不能读写就派出异常
只能使用try except 看是否可以读写
在非阻塞io中 需要不断循环询问操作是否需要处理的数据
这样一来 对应程序而言 效率确实高了
但是操作系统而言 你的程序就像一个病毒 一直强行占用cpu
当你的TCP程序 没有连接 没有数据接受 没有数据发送时 就是在做无用循环 浪费系统资源



4.多路复用 待改:有中间select recv等待结果
核心函数select
帮你检测所有的连接 找出可以被处理(可以读写)的连接
作为处理数据的一方 不再需要重复去向系统询问 select给你谁,你就用谁来处理


举例待改

举例1:迭代期间不能修改被迭代的对象
# li = [1,2,3,4,5,6]
# def mytlist_iter():
# for i in range(len(li)):
# yield li[i]
# for j in mytlist_iter():
# if j == 5:
# li.remove(5)
# d = {"a":1,"b":2}
# for k in d:
# if k == "a":
# d.pop(k)

举例2:(多路复用)
服务端:
from concurrent.futures import ThreadPoolExecutor
import socket

server = socket.socket()
# 重用端口
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

server.bind(("192.168.11.210",9999))

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 = []
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 ConnectionResetError:
# 对方关闭了连接
c.close()
# 从客户端列表中删除它
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 ConnectionResetError:
# 客户端连接需要删除
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()

客户端:
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模型
2.非阻塞IO模型
协程是一种非阻塞IO
1.setblocking(False)将阻塞修改非阻塞
2.一旦是非阻塞 在执行accept recv send 就会立马尝试读写数据 一旦数据没有准备
好就抛出异常
3.捕获异常
4.如果没有异常说明数据准备好了 直接处理
5.捕获到异常 那就做别的事情
可以实现单线程并发的效果 会大量占用CPU资源

3.多路复用
将所有连接家给select来管理 管什么? 管哪个连接可以被及处理
作为处理任务的乙方事情变少了 不需要重复不断的问操作系拿数据 而是等待select返回需要处
理的连接
等待则意味着select是阻塞的

3.1 创建连接和管理连接
1.创建服务器socket对象
2.将服务器对象交给select来管理
3.一旦有客户端发起连接 select将不在阻塞
4.select将返回一个可读的socket对象(第一次只有服务器)
5.服务器的可读代表有连接请求 需要执行accept 返回一个客户端连接conn 由于是非阻塞 不能立
即去recv
6.把客户端socket对象也交给select来管理 将conn加入两个被检测的列表中
7.下一次检测到可读的socket可能是服务器 也可能是客户端 所以加上判断 服务器就accept 客户端
就revc
8.如果检测到有可写(可以send就是系统缓存可用)的socket对象 则说明可以向客户端发送数据了
7 and 8 的执行顺序是不固定的

3.2 处理数据收发
两个需要捕获异常的地方
1.recv 执行第七步 表示可以读 为什么异常 只有一种可能是客户端断开连接
还需要加上 if not 判断是否有数据 linux下 对方下线不会抛出异常 会收到空消息
2.send 执行第八步 表示可以写 为什么异常 只有一种可能客户端断开连接






异步IO 不仅仅指网络IO 也包括本地IO
非阻塞IO 和多路复用 解决的都是网络IO的阻塞问题
本地IO 可以通过子线程 或子进程 来避免阻塞 但是对子线程或者子进程而言 依旧会阻塞

最终的解决方案就是协程 asyncio 该模块实现异步IO 内部使用协程实现
原文地址:https://www.cnblogs.com/yanhui1995/p/9960038.html