IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间 --> 内核空间、内核空间-->设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。
通俗理解:让CPU等待的就是IO,而输入/输出就是一个等待的过程,也可以将IO理解为就是阻塞
IO模型就是为了说明协程遇到IO操作切换执行另一个函数,当IO执行完后是如何切换回来执行原来的函数的
5种IO模型:
- 阻塞IO ->blocking IO -> 工作效率低
- 非阻塞IO -> nonblocking IO -> 工作效率高,CPU负担大 -> 不建议使用
- IO多路复用-> IO multiplexing -> 在有多个对象需要IO阻塞的时候,能够有效的减少阻塞带来的时间损耗,且能够在一定程度上减少CPU的负担
- 异步IO-> asynchronous IO -> 工作效率高,CPU负担少,无法直接通过代码实现,只能借助操作系统一些机制/接口或Python的内置模块实现,asyncio是Python的内置异步IO模块,通过它就能实现异步IO
- 信号驱动IO-> signal driven IO -> 并不常用
1. 阻塞IO

# 平时我们写代码遇到的就是IO阻塞,如 recv
2. 非阻塞IO

- 通过代码实现非阻塞IO
# server.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
sk.setblocking(False) # 将socket设置为非阻塞模式
conn_lis = []
while True:
try:
conn, addr = sk.accept()
conn_lis.append(conn)
except BlockingIOError:
del_lis = []
for c in conn_lis:
try:
msg = c.recv(10).decode('utf-8')
if not msg:
c.close()
del_lis.append(c)
else:
print(msg)
c.send(msg.upper().encode('utf-8'))
except BlockingIOError:
pass
if del_lis:
for del_item in del_lis:
conn_lis.remove(del_item)
# client.py
import socket
import time
from threading import Thread
def fun():
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
time.sleep(1)
sk.send(b'hello')
print(sk.recv(10).decode('utf-8'))
sk.close()
for i in range(10):
Thread(target=fun).start()
3. IO多路复用

- 如果只有一个IO阻塞的时候就不建议使用IO多路复用,直接使用IO阻塞就可以了,因为IO多路复用只有在有多个对象需要IO阻塞的时候使用,只有一个IO阻塞的时候使用IO多路复用反而会降低了执行效率(因为IO多路复用执行的过程中中间还多了一个通知用户态的过程)
- 使用select模块实现IO多路复用
# server.py
import socket
import select
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
read_lst = [sk] # 输入列表
while True:
rl, wl, xl = select.select(read_lst, [], []) # 监听输入列表中sk或多个conn是否有用户进行连接(是否有数据返回),如果有就会返回一个列表中包含 以被用户连接的sk 或 多个得到数据的conn对象 (因为有多个客户端对服务端进行连接,所以才会有多个conn对象)
for item in rl: # 循环select的返回值中的输入列表,因为里面包含了 以被用户连接的sk 或 多个得到数据的conn对象(因为有多个客户端对服务端进行连接,所以才会有多个conn对象)
if item == sk: # 判断是否是sk对象(因为sk对象只会有一个,并且是同一个),如果等于sk对象那么就代表有新的客户端对服务端进行连接
conn, addr = item.accept() # 得到一个conn客户端对象
read_lst.append(conn) # 将 与服务端进行链接了的客户端对象放入 read_lst 输入列表中
else: # 如果不是sk对象就是conn对象
ret = item.recv(1024).decode('utf-8')
if not ret: # 判断接收到的消息是否为空,如果是就说明客户端已经断开连接这样就关闭该客户端并且从read_lst输入列表中删除,如果不为空就打印该数据并且向客户端回一条消息
item.close()
read_lst.remove(item)
else:
print(ret)
item.send(ret.upper().encode('utf-8'))
# client.py
import socket
import time
from threading import Thread
def fun():
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
for i in range(10):
time.sleep(2)
sk.send(b'hello')
print(sk.recv(1024))
sk.close()
for i in range(10):
Thread(target=fun).start()