如何在关机期间强制断开所有当前连接的客户端与我的 TCP 或 HTTP 服务器的连接?

How do I forcibly disconnect all currently connected clients to my TCP or HTTP server during shutdown?

我有一个假的 HTTP 服务器,我在测试中用作固定装置。在测试的某个时刻,我想停止服务器,而不管任何仍然打开的连接。这些打开的连接上的客户端应该获得 TCP FIN。

我知道生产服务器通常需要解决不同的问题,即 quiescing,有时称为正常 shutdown。这与我想要的相反。

对于独立进程,通常可以简单地让进程退出,OS 将处理剩下的事情。 (强行杀死进程很容易,而强行杀死线程则不是。)但是,我的假服务器 运行 在测试进程本身的线程中,所以我没有这个选项(而且我没有如果有其他方法,我想将其外部化)。

我在 Python 和 HTTPServer class 调查了这个问题,但我找不到任何解决方案。

我也在 Go 中对此进行了调查,在那里我找到了 Contexts 的概念,它接近我所需要的,但它的工作方式相反:http 服务器将传播 Context可用于取消,例如如果客户端断开连接,则进行数据库查找。

编辑:看起来 Go 实际上做了我需要的,并且有一个单独的优雅和非优雅关闭方法,非优雅是 net/http#Server.Close

server = http.server.HTTPServer(...)
thread = threading.Thread(run=server.serve_forever)
thread.start()

# a client has connected ....

server.shutdown()
# at this point I want to have the server stopped,
# without waiting for the request handling to complete

我已经在 Python 中实现了 Go 解决方案。当新客户端连接时,我记住客户端套接字,当我想退出时,我关闭所有记住的套接字。

似乎有效。

import socket
import http.server.HTTPServer

class MyHTTPServer(HTTPServer):
    """Adds a method to the HTTPServer to allow it to exit gracefully"""

    def __init__(self, addr, handler_cls):
        super().__init__(addr, handler_cls)
        self._client_sockets: List[socket.socket] = []

        self.server_killed = False

    def get_request(self) -> Tuple[socket.socket, Any]:
        """Remember the client socket"""
        sock, addr = super().get_request()
        self._client_sockets.append(sock)
        return sock, addr

    def shutdown_request(self, request: socket.socket) -> None:
        """Forget the client socket"""
        self._client_sockets.remove(request)
        print(f"{self._client_sockets=}")
        super().shutdown_request(request)

    def force_disconnect_clients(self) -> None:
        """Shutdown the remembered sockets"""
        for client in self._client_sockets:
            client.shutdown(socket.SHUT_RDWR)

用法

server = MyHTTPServer(server_addr, MyRequestHandler)

# in a new thread
while not server.server_killed:
    self._server.handle_request()

# ... use the server (keep in mind it can have at most one client at a time) ...

# in the main program
server.server_killed = True
server.force_disconnect_clients()
server.server_close()