io多路复用

首先需要明确的是,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()
image.png
image.png

挂个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的情况下,还是同步的操作。

推荐阅读更多精彩内容