如何优雅关闭存在客户端连接的Python TCP服务器?
解决Python TCP服务器优雅关闭的阻塞问题
你的问题核心在于阻塞式的recv调用会让客户端线程一直挂起,即使主线程调用了shutdown(),这些子线程也不会主动退出,直到客户端断开或者recv被强制打断。下面是一套完整的解决方案,兼顾优雅性和可靠性:
核心思路
- 跟踪所有活跃的客户端套接字,在关闭服务器时主动关闭它们,打破
recv的阻塞状态 - 给客户端线程添加退出检查,确保收到关闭信号后能快速退出循环
- 可选但推荐:给套接字设置超时,让线程能定期检查退出标志,避免完全卡死
修改后的完整代码示例
1. 扩展ThreadedTCPServer,跟踪活跃套接字
首先我们需要在服务器类里维护一个线程安全的集合,用来记录所有正在处理的客户端套接字:
import threading import socketserver import socket def createContentHandler(server = None): """ Creates TCP Request Handler with given message handler """ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): def handle(self): thread_id = threading.get_ident() sock = self.request # 设置套接字超时,让线程能定期检查退出状态 sock.settimeout(1.0) # 将当前套接字加入服务器的活跃集合 with self.server.lock: self.server.active_sockets.add(sock) try: while True: # 先检查服务器是否已发起关闭请求 if self.server._shutdown_request: print(f"Thread {thread_id} exiting due to server shutdown") break msg = self.receiveMessage() data = msg.decode("utf-8") response = server._msghandler.handle(data) self.sendMessage(response) except ConnectionResetError as e: print(f"Thread {thread_id}: socket closed by client") except socket.timeout: # 超时后继续循环,会再次检查退出标志 pass except Exception as e: print(f"Thread {thread_id}: socket closed by other reason: {str(e)}") finally: # 清理:从集合移除套接字并关闭 with self.server.lock: self.server.active_sockets.discard(sock) sock.close() def receiveMessage(self): sock = self.request try: data = sock.recv(1024) if not data: raise ConnectionResetError("Client closed connection") return data except socket.timeout: # 抛出超时异常,让handle方法处理 raise def sendMessage(self, response): sock = self.request sock.sendall(response.encode("utf-8")) return ThreadedTCPRequestHandler class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): """ Handle each request in a separate thread. """ def __init__(self, server_address, RequestHandlerClass): super().__init__(server_address, RequestHandlerClass) self.active_sockets = set() self.lock = threading.Lock() class Socket_Server(): def __init__(self, *, address, port, msghandler): # 修正拼写:adress -> address super(Socket_Server, self).__init__() self._address = address self._port = port self._msghandler = msghandler self._server = None def run(self): print('TCP socket server starting...') server_address = (self._address, self._port) tcpHandler = createContentHandler(self) print('TCP socket server is running.') with ThreadedTCPServer(server_address, tcpHandler) as server: self._server = server server.serve_forever(0.5) def shutdown(self): if self._server is not None: # 1. 通知服务器停止接受新连接 self._server.shutdown() # 2. 关闭所有活跃客户端套接字,打破recv阻塞 with self._server.lock: # 用list()避免遍历过程中集合变化 for sock in list(self._server.active_sockets): try: sock.close() print(f"Closed active client socket") except Exception as e: print(f"Error closing client socket: {str(e)}") # 3. 关闭服务器套接字 self._server.server_close() self._server = None print("Server is shut down")
关键修改点解析
- 跟踪活跃套接字:
ThreadedTCPServer新增了active_sockets集合和线程锁,确保多线程环境下的安全操作 - 套接字超时:给每个客户端套接字设置1秒超时,这样即使没有主动关闭,线程也能每隔1秒检查一次服务器的关闭状态
- 主动关闭客户端套接字:在
shutdown()方法中,遍历所有活跃套接字并关闭,直接打破recv的阻塞状态 - 退出检查:在
handle循环的开头先检查服务器的_shutdown_request标志,确保线程能快速响应关闭命令
测试方式
- 启动服务器,连接一个或多个客户端
- 调用
Socket_Server.shutdown() - 你会看到服务器立即输出关闭信息,所有客户端线程也会快速退出,进程不再挂起
内容的提问来源于stack exchange,提问作者MichaelW
相关产品推荐
相关产品推荐

