求通俗讲解Python的select、selectors模块在socket开发中的作用
通俗理解Python的select、selectors库(socket服务端场景)
首先我们先从你写原生socket服务端的常见痛点讲起:
- 你写最基础的TCP服务端时,通常会循环调用
accept()等待客户端连接,连接建立后再调用recv()等待客户端发数据对吧? - 这时候就会碰到阻塞IO的问题:如果没有客户端连接,程序会直接卡死在
accept();有一个客户端连上但没发数据,程序又卡死在recv(),其余客户端完全没法接入,同一时间只能处理一个连接。
1. select库的核心作用
你可以把select理解成一个「socket状态巡检员」:你提前把所有需要监控的socket(用来监听新连接的服务端socket、已经建立连接的客户端socket)都交给它,它会帮你盯着这些socket的状态变化,直到至少有一个socket满足你要的条件(比如有新连接进来、有客户端发数据了)才会返回,你再挨个处理这些就绪的socket即可。
- 这就是最基础的IO多路复用实现,不用再死等某一个socket,同一时间可以处理上百个连接。
- Python的
select.select()方法有三个核心入参,分别是三类你要监控的socket列表:「等待读就绪的socket列表」、「等待写就绪的socket列表」、「等待异常的socket列表」,返回值就是三类中已经就绪的socket列表,直接遍历处理即可。 - 它的缺点也很明显:每次调用都要传入全量的待监控socket列表,内部要遍历所有socket检查状态,连接数越高效率越低,而且默认最多只能监控1024个socket。
2. selectors库的核心作用
selectors是Python对底层多路复用机制的高层封装,它会自动根据你当前的操作系统选择最优的多路复用实现:Linux下用epoll、BSD/macOS下用kqueue、Windows下默认用select,不需要你自己做平台适配。
- 用法比原生select简单很多:你只要创建一个
selectors.DefaultSelector()实例,调用register()方法把待监控的socket、要监控的事件(读/写)、对应的回调函数传进去即可,之后循环调用select()就能拿到所有就绪的事件,直接执行对应回调就行。 - 性能远优于原生select,没有1024个连接的上限,也不用每次都传入全量的socket列表。
极简使用示例对比
原生select实现简易服务端
import select import socket server_sock = socket.socket() server_sock.bind(('0.0.0.0', 8080)) server_sock.listen(100) # 初始化待监控的读就绪socket列表,初始只有服务端监听socket read_list = [server_sock] while True: # 阻塞到至少有一个socket就绪 readable, _, _ = select.select(read_list, [], []) for sock in readable: if sock is server_sock: # 有新连接接入,加入监控列表 conn, addr = sock.accept() read_list.append(conn) else: # 客户端发来了数据 data = sock.recv(1024) if not data: sock.close() read_list.remove(sock) else: print(f"收到数据:{data.decode()}")
selectors实现简易服务端
import selectors import socket sel = selectors.DefaultSelector() server_sock = socket.socket() server_sock.bind(('0.0.0.0', 8080)) server_sock.listen(100) def accept_conn(sock): conn, addr = sock.accept() # 注册新连接的读事件,绑定接收数据的回调 sel.register(conn, selectors.EVENT_READ, handle_recv) def handle_recv(conn): data = conn.recv(1024) if not data: sel.unregister(conn) conn.close() else: print(f"收到数据:{data.decode()}") # 先注册服务端监听socket的读事件,绑定接入连接的回调 sel.register(server_sock, selectors.EVENT_READ, accept_conn) while True: # 阻塞获取所有就绪事件 events = sel.select() for key, mask in events: # 直接执行对应回调 callback = key.data callback(key.fileobj)
内容的提问来源于stack exchange,提问作者Lionheart2007
相关产品推荐
相关产品推荐

