共计 1388 个字符,预计需要花费 4 分钟才能阅读完成。
在网络编程中,如果我们希望一个服务器同时响应多个客户端连接请求,并在各自的连接中处理数据,最简单的方式是为每个连接开一个线程或进程,但这样会迅速耗尽资源。更高效的做法是使用 I/O 多路复用技术。Python 提供了 select 模块来支持这种模式。
一、什么是 I/O 多路复用
I/O 多路复用(Multiplexing)是一种让单个进程可以监控多个 socket 连接的技术。它通过监听一组 socket 的状态变化,在某个 socket 可读或可写时通知应用程序执行对应操作。
二、select 模块概述
select.select(rlist, wlist, xlist[, timeout]) 接收三个列表参数,返回三个列表:
rlist:等待可读的 socket 列表wlist:等待可写的 socket 列表xlist:等待异常的 socket 列表
返回结果为三个列表,表示当前就绪的 socket。
三、TCP 多客户端通信示例
import socket
import select
# 创建 TCP 服务器 socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 9000))
server.listen(5)
server.setblocking(False) # 设置非阻塞模式
# 存放所有客户端 socket
inputs = [server]
print(" 服务器启动,监听端口 9000")
while True:
readable, _, _ = select.select(inputs, [], [])
for sock in readable:
if sock is server:
conn, addr = server.accept()
print(" 新连接:", addr)
conn.setblocking(False)
inputs.append(conn)
else:
try:
data = sock.recv(1024)
if data:
print(" 收到:", data.decode())
sock.send(b" 服务器已收到您的消息 ")
else:
print(" 客户端断开连接 ")
inputs.remove(sock)
sock.close()
except:
inputs.remove(sock)
sock.close()
四、关键点解析
- 设置 socket 为非阻塞模式:
setblocking(False),防止某个连接阻塞整个程序。 select.select()监听多个 socket,一旦某个 socket 可读,即执行数据处理。- 无需为每个连接新开线程,节省系统资源。
五、与多线程 / 多进程比较
| 方式 | 优点 | 缺点 |
|---|---|---|
| 多线程 | 简单直观,逻辑清晰 | 线程数多时调度复杂,性能下降 |
| 多进程 | 稳定性高 | 资源开销大 |
| select 多路复用 | 高效,适合大量连接 | 编程复杂度略高 |
六、练习建议
- 尝试将之前的 TCP 聊天程序修改为基于
select的方式。 - 结合字典管理多个客户端的昵称,实现简单群聊功能。
- 加入消息广播功能,让一个客户端的消息能被所有客户端接收。
多路复用技术是构建高并发服务器的核心之一,理解其原理和应用场景,将为后续学习异步框架如 asyncio、Twisted 打下基础。明天我们将探索 asyncio 的使用。
正文完