socket非阻塞I/O与select/epoll事件驱动
请解释Python socket编程中阻塞I/O与非阻塞I/O的区别,以及如何使用select/poll/epoll实现事件驱动的多连接处理。
回答
编译有声
阻塞I/O:默认模式,accept()、recv()、send()等调用会阻塞当前线程直到操作完成。每个连接需要一个线程,C10K问题。
非阻塞I/O:socket.setblocking(False)设置,操作立即返回,如果无法完成抛出BlockingIOError或WouldBlockError。需要轮询检查状态。
select/poll/epoll事件驱动:
select:跨平台(Windows/Linux/Mac),监视最多1024个fd,每次调用需将fd集合从用户态复制到内核态,线性扫描fdpoll:类似select但无1024限制,仍是线性扫描epoll(Linux专属):epoll_create()创建epoll实例epoll_ctl()注册/修改/删除fd事件epoll_wait()等待事件,返回就绪fd列表(O(1)复杂度),无需扫描全部fd- 边缘触发(ET)vs 水平触发(LT):ET模式仅在状态变化时通知,需一次性读完
示例:
import selectors, socket
sel = selectors.DefaultSelector() # 自动选最佳(Linux用epoll)
def accept(sock):
conn, addr = sock.accept()
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read)
def read(conn):
data = conn.recv(1024)
if data:
conn.send(data)
else:
sel.unregister(conn)
conn.close()
server = socket.socket()
server.bind(('localhost', 8888))
server.listen()
server.setblocking(False)
sel.register(server, selectors.EVENT_READ, accept)
while True:
for key, mask in sel.select():
key.data(key.fileobj) # 调用回调