学习I/O模型,主要实现单线程条件下遇到I/O如何解决效率提升问题,也是协程要处理的问题。
阻塞I/O
我们在平时进行的socket编程时,链路循环中服务端接收链接,以及通信循环中服务端接收数据时,就是典型的阻塞I/O模型(主要服务端)。
代码示例
from
socket import *
s =
socket(AF_INET, SOCK_STREAM)
s.
setsockopt(SOL_SOCKET, SO_REUSEADDR,
1)
s.
bind((
'127.0.0.1',
8080))
s.
listen(
5)
while 1:
conn, addr =
s.
accept()
while 1:
try:
data = conn.
recv(
1024)
if not data:
break
conn.
send(data.upper())
except Exception:
break
conn.
close()
s.
close()
代码解析
主要两个地方处于阻塞状态:
s.accept():该状态下server套接字等待操作系统把conn连接发给它,但是 操作系统一直在等待客户端链接,这个阶段处于阻塞状态。data = conn.recv(1024):该状态下conn套接字等待操作系统把数据拷贝给它,操作系统自己也需要等待客户端发送数据过来,这也使整个程序处于阻塞状态。
先贴张图:
IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段,记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况:
等待数据准备 (Waiting for the data to be ready)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
非阻塞I/O
非阻塞I/O指在应用程序在操作系统wait for data阶段不需要等待,发一个系统调用给操作系统询问操作系统是否有数据,没有则会去干别的事情,有的话就接收一下数据。
代码示例
from socket
import *
s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR,
1)
s.setblocking(
False)
s.bind((
'127.0.0.1',
8080))
s.listen(
5)
conn_l = []
del_conn = []
while 1:
try:
conn, addr = s.accept()
conn_l.append(conn)
except Exception:
for conn
in conn_l:
try:
data = conn.recv(
1024)
conn.send(data.upper())
except BlockingIOError:
pass
except ConnectionResetError:
for conn
in del_conn:
conn.close()
del_conn.remove(conn)
del_conn = []
代码解读
设置s.setblocking(False),套接字为非阻塞状态,这样server套接字不会等操作系统有数据才继续执行。没有链接数据会抛一个异常,让程序继续向下执行,走的是通讯循环的逻辑。如果有数据则会将conn加到列表里面进行链接保存。此种方式占用CPU过多,不推荐使用
I/O多路复用
代码示例
from
select import
select
from
socket import *
s =
socket(AF_INET, SOCK_STREAM)
s.
setsockopt(SOL_SOCKET, SO_REUSEADDR,
1)
s.
bind((
'127.0.0.1',
8080))
s.
listen(
5)
select_l = [
s, ]
while 1:
selected_l,
_,
_ =
select(select_l, [], [])
print(selected_l)
for selected_item in selected_l:
if selected_item ==
s:
conn, addr = selected_item.
accept()
select_l.append(conn)
else:
data = selected_item.
recv(
1024)
selected_item.
send(data.upper())
注意问题
select模块会自动过滤没有数据的套接字,有的话会将该套接字放入子集里面。根据加入的套接字进行判断,是server套接字就取出链接和地址,是conn套接字就接收发送数据。select模块工作是基于列表去筛选哪个套接字有数据,效率较低,而且有套接字监控数量的限制,而poll只是对套接字监控数量做了扩展,没有实现高效筛选套接字的机制。
实现I/O多路复用的机制
select、poll、epoll 其中epoll克服了select和poll筛选效率低的问题,套接字数据有了就主动通知调用者进行处理,通过回调函数机制实现效率的大幅提升。但是只在linux平台下可以使用。这样selectors模块应运而生!它可以根据用户使用不同的平台来自动选择该使用select还是epoll,是不是很强大呢?! 待续~