You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优雅关闭存在客户端连接的Python TCP服务器?

解决Python TCP服务器优雅关闭的阻塞问题

你的问题核心在于阻塞式的recv调用会让客户端线程一直挂起,即使主线程调用了shutdown(),这些子线程也不会主动退出,直到客户端断开或者recv被强制打断。下面是一套完整的解决方案,兼顾优雅性和可靠性:

核心思路

  1. 跟踪所有活跃的客户端套接字,在关闭服务器时主动关闭它们,打破recv的阻塞状态
  2. 给客户端线程添加退出检查,确保收到关闭信号后能快速退出循环
  3. 可选但推荐:给套接字设置超时,让线程能定期检查退出标志,避免完全卡死

修改后的完整代码示例

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标志,确保线程能快速响应关闭命令

测试方式

  1. 启动服务器,连接一个或多个客户端
  2. 调用Socket_Server.shutdown()
  3. 你会看到服务器立即输出关闭信息,所有客户端线程也会快速退出,进程不再挂起

内容的提问来源于stack exchange,提问作者MichaelW

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:48:45