diff --git a/pymsg/README.md b/pymsg/README.md index c5694a8..ad76e58 100644 --- a/pymsg/README.md +++ b/pymsg/README.md @@ -142,6 +142,7 @@ server.start(on_message) # 阻塞当前线程 | `addr` | 监听地址 | | `port` | 监听端口 | | `workers` | 工作线程数(关键字参数,默认 `0`);`>0` 时回调在线程池中并发执行 | +| `recv_timeout` | 接收单条客户端消息的超时秒数(关键字参数,默认 `10.0`);超时未收到完整消息则主动断开连接,防御 Slowloris 式半开连接攻击 | 回调签名为 `on_message(sock, addr, text)`: - `sock` — 客户端 socket,可用 `reply()` 回复 @@ -160,6 +161,8 @@ server.start(on_message) # 阻塞当前线程 服务端内部管理客户端列表,透明处理断连和管道破裂,使用 `select` 实现非阻塞 I/O,轮询间隔 100ms。 +**Slowloris 防护**:每个客户端连接在 `recv_timeout`(默认 10 秒)内必须发送完一条完整消息(4 字节头 + 载荷),超时未完成则服务端主动断开该连接,防止慢速发送攻击阻塞主事件循环。 + **并发模式(v0.2.0)**: ```python diff --git a/pymsg/pymsg/__init__.py b/pymsg/pymsg/__init__.py index 72dd332..48d4e7e 100644 --- a/pymsg/pymsg/__init__.py +++ b/pymsg/pymsg/__init__.py @@ -296,10 +296,14 @@ class MsgServer: 经每连接发送锁串行化,不会发生帧交错。 """ - def __init__(self, addr: str, port: int, *, workers: int = 0) -> None: + _RECV_TIMEOUT = 10.0 # 服务端接收单条消息的超时(秒),防止 Slowloris 式半开连接卡死主线程 + + def __init__(self, addr: str, port: int, *, workers: int = 0, + recv_timeout: float = _RECV_TIMEOUT) -> None: self.addr = addr self.port = port self._workers = workers + self._recv_timeout = recv_timeout self._running = False self._server_sock: "_socket.socket | None" = None self._clients: "dict" = {} # socket -> (ip, port) @@ -429,8 +433,9 @@ class MsgServer: def _handle_client(self, s, socketlist, on_message): try: - text = recv(s) - except (ConnectionError, ConnectionResetError, OSError): + text = recv(s, timeout=self._recv_timeout) + except (ConnectionError, ConnectionResetError, OSError, TimeoutError): + # TimeoutError: 客户端在超时时间内未发送完整消息(Slowloris 防护),关闭连接 self._remove_client(s, socketlist) return addr = self._clients.get(s, ("unknown", 0))