并发05--网络IO模型
IO模型简介
# 我们这里研究的IO模型都是针对网络IO
Stevens在文章中一共比较了五种IO Model:
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用
* asynchronous IO 异步IO
* signal driven IO 信号驱动IO
# 由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model
# 1.等待数据准备 (Waiting for the data to be ready)
# 2.将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
同步异步
阻塞非阻塞
# 常见的网络阻塞状态:
accept # tcp的等待链接
recv # tcp的接受数据
recvfrom # udp的接受数据
send # 虽然它也有io行为 但是不在我们的考虑范围(是主动的操作,且IO时间短)
简单的网络数据传输过程-图解
recv的过程(被动的等待):
1.阻塞IO模型
官方图解
# 阻塞IO模型
程序在接受到数据之前,都处于阻塞状态,只有接受到数据后,才会往下执行
# 我们之前写的都是阻塞IO模型 协程除外
import socket
# 服务端
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn, addr = server.accept()
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data)
conn.send(data.upper())
except ConnectionResetError as e:
break
conn.close()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data)
# 在服务端开设多进程或者多线程 进程池线程池 其实还是没有解决网络IO问题
该等的地方还是得等 没有规避
每个人都会在该等的地方,还是会等待,只不过多个人等待的彼此互不干扰
2.非阻塞IO
官方图解
这样做的好处:程序不会因IO操作进入阻塞态,虽然返回结果的数据依旧没有拿到,但可以一直占着CPU,切换进行其他的操作。
# 非阻塞IO模型
程序在系统调用时(跟操作系统内核要数据),无论是否获取到数据,都立刻返回一个结果
程序会根据结果(是否获取到数据),进行不同的操作(继续调用或向下执行),不会一直处于阻塞状态
# eg: 要自己实现一个非阻塞IO模型
import socket
import time
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
# 将所有的网络阻塞变为非阻塞
server.setblocking(False)
r_list = []
del_list = [] # 临时将需要无用的连接删除
while True:
try:
conn, addr = server.accept()
r_list.append(conn)
# 这里并不是真的出错,而是阻塞变成非阻塞后,系统调用后,没有数据时的反馈(BWOULDBLOCK)
except BlockingIOError:
# time.sleep(0.1)
# print('列表的长度:',len(r_list))
# print('做其他事')
for conn in r_list:
try:
data = conn.recv(1024) # 没有消息 报错
if len(data) == 0: # 客户端断开链接
conn.close() # 关闭conn
# 将无用的conn从r_list删除
del_list.append(conn)
continue
conn.send(data.upper())
except BlockingIOError:
continue
except ConnectionResetError:
conn.close()
del_list.append(conn)
# 回收无用的链接
for conn in del_list:
r_list.remove(conn)
del_list.clear()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8081))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data)
总结
虽然非阻塞IO给你的感觉非常的牛逼
但是该模型会 长时间占用着CPU并且不干活 让CPU不停的空转
我们实际应用中也不会考虑使用非阻塞IO模型
任何的技术点都有它存在的意义
实际应用或者是思想借鉴
3.IO多路复用***
官方图解
# IO多路复用模型
操作系统内部提供一个监管机制,能够帮程序监管socket和conn对象,
并且可以监管多个,只有系统数据获取到,就会返回对应的监管对象
当监管的对象只有一个的时候 其实IO多路复用连阻塞IO都比比不上!!!
但是IO多路复用可以一次性监管很多个对象
# 存在IO阻塞的部分是 accept 和 recv ,分别是server对象和conn对象
server = socket.socket()
conn,addr = server.accept()
# eg: 手动实现IO多路复用
监管机制(内部实现其实跟非阻塞IO一样)是操作系统本身就有的
如果你想要用该监管机制(select) 需要你导入对应的select模块
import socket
import select
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)
read_list = [server]
while True:
r_list, w_list, x_list = select.select(read_list, [], [])
# select返回的是一个元祖,元祖中的第一个列表包含 监管到的 多个对象。
"""
帮你监管,一旦有人来了 立刻给你返回对应的监管对象
"""
# 此时,若有server连接对象,就会被select监管到 在返回的元祖第一个列表中
# res = select.select(read_list, [], [])
# print(res) # ([<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>], [], [])
# print(server) # 和select返回的结果,和server对象一样
# print(r_list) # 因为做了循环监管,此时就不仅仅只有server对象,还有conn对象。所以后续做了对象的判断。
for i in r_list: #
"""针对不同的对象做不同的处理"""
if i is server: # server对象就连接操作
conn, addr = i.accept()
# conn 也应该添加到监管的队列中
read_list.append(conn)
else: # conn对象就行通信操作
res = i.recv(1024)
if len(res) == 0:
i.close() # 关闭conn连接
# 将无效的监管对象 移除
read_list.remove(i)
continue
print(res)
i.send(b'heiheiheiheihei')
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data)
总结
# 监管机制其实有很多
1.select机制 windows linux都有
2.poll机制 只在linux有 poll和select都可以监管多个对象 但是poll监管的数量更多
上述select和poll机制其实都不是很完美 当监管的对象特别多的时候
可能会出现 极其大的延时响应
3. epoll机制 只在linux有 # redis 就是采用 IO多路复用模型 + epoll机制
它给每一个监管对象都绑定一个回调机制
一旦有响应 回调机制立刻发起提醒
针对不同的操作系统还需要考虑不同检测机制 书写代码太多繁琐
有一个人能够根据你跑的平台的不同自动帮你选择对应的监管机制
selectors模块
# 面试回答:
1.IO模型的select、poll、epoll 都是 IO多路复用模型
2.select 监听的最大数是 1024, 是一个轮询 比较耗资源 Windows基本都是select
3.poll 没有监听数据的限制 也是一个轮询
4.epoll 首先只能在linux上使用,不是轮询,是一个主动回调的过程
# 为啥redis 不支持window
1.Windows官方不支持
2.redis 采用的IO多路复用 的epoll模型,Windows上使用的select,不支持epoll
3.有人将redis迁移到Windows上 走得就是select,性能 就没有linux上高
4.异步IO
官方图解
# 异步IO模型
程序在进行系统调用时(获取数据),是异步操作,cpu切换,进行其他操作
根据系统调用的回调机制,再执行 接受到数据的处理
# 异步IO模型是所有模型中效率最高的 也是使用最广泛的
相关的模块和框架
模块: asyncio模块
异步框架:sanic tronado twisted
速度快!!!
import threading
import asyncio # 游戏、交互式画面、爬虫
@asyncio.coroutine
def hello():
print('hello world %s'%threading.current_thread())
yield from asyncio.sleep(1) # 换成真正的IO操作
print('hello world %s' % threading.current_thread())
loop = asyncio.get_event_loop()
tasks = [hello(),hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
四个IO模型对比
官方图解
参考博客园图解,稍微了解即可
https://www.cnblogs.com/Dominic-Ji/articles/10929396.html