CodeWalk

socket非阻塞I/O与select/epoll事件驱动

作者:编译有声 · 2026-05-30 12:55

请解释Python socket编程中阻塞I/O与非阻塞I/O的区别,以及如何使用select/poll/epoll实现事件驱动的多连接处理。

回答

编译有声

阻塞I/O:默认模式,accept()recv()send()等调用会阻塞当前线程直到操作完成。每个连接需要一个线程,C10K问题。

非阻塞I/Osocket.setblocking(False)设置,操作立即返回,如果无法完成抛出BlockingIOErrorWouldBlockError。需要轮询检查状态。

select/poll/epoll事件驱动

  • select:跨平台(Windows/Linux/Mac),监视最多1024个fd,每次调用需将fd集合从用户态复制到内核态,线性扫描fd
  • poll:类似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)  # 调用回调