首先需要明确的是,linux有五类io模型
1.阻塞
2.非阻塞
3.io多路复用
4.事件驱动
5.异步
(ps:这里需要的点是:io多路复用和非阻塞是并列的关系哦~,不过一般来说io多路复用都是和非阻塞搭配使用的。)
最容易理解的是阻塞。一次网络io时,C端发出请求,S端收到。当C端发出一个请求,进行io时,就不能进行其他操作了,需要同步的等待结果的返回。
当使用io多路复用时,有多个C端同时发送请求,这些IO操作会被selector(epoll,kqueue)给暂时挂起,入内存队列。此时S端可以自己选择什么时候读取、处理这些io,也就是说S端可以同时hold住多个io。
epoll伪代码:
while True:
sel_results = select_ready() # block
for r in sel_results:
deal_with_it_util_not_available()
挂个demo
# coding: utf-8
# blocking / epoll io client
import selectors
import socket
sel = selectors.DefaultSelector()
def accept(sock, mask):
conn, addr = sock.accept() # Should be ready,拷贝到内存空间,并分配一个新的socket,给当前接入的请求
print('accepted', addr)
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read) # 将这个sokcet注册到 sel,也就是sel也可以开始监听这个sock,并注册回调,等待下次轮训
def read(conn, mask):
data = conn.recv(1000) # 下次轮训,执行这个回调
if data:
print('echoing', conn)
conn.send(data) # 回传数据给请求方。
print('send over', conn)
else:
print('closing', conn)
sel.unregister(conn) # 请求结束,删除socket监听
conn.close()
sock = socket.socket() # 绑定一个socket
sock.bind(('localhost', 12325))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept) # selector注册监听这个socket。可以把这个sokcet当作一个流。一直监听的只有这个接收请求的socket
i = 1
import time
while True: # 轮训sel的ready事件
events = sel.select()
time.sleep(10)
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
print('this is ', i)
i += 1
输出结果:
*accepted* ('127.0.0.1', 34154)
this is 1
*accepted* ('127.0.0.1', 34550)
echoing <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12325), raddr=('127.0.0.1', 34154)>
send over <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12325), raddr=('127.0.0.1', 34154)>
this is 2
*accepted* ('127.0.0.1', 34732)
echoing <socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12325), raddr=('127.0.0.1', 34550)>
send over <socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12325), raddr=('127.0.0.1', 34550)>
this is 3
echoing <socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12325), raddr=('127.0.0.1', 34732)>
send over <socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12325), raddr=('127.0.0.1', 34732)>
this is 4
可以观察到,sel.select()每次选出一个sock,然后对这个sock上的事件进行处理。所以io多路复用,在没有用特殊异步api的情况下,还是同步的操作。