TCP 侦听器关闭时检测问题

Issue detecting when TCP listener has been closed

我正在创建自定义 TCP/IP client/server 应用程序,但在尝试停止服务器时 运行 遇到了麻烦。最初,我的代码使用一个 TcpListener 来监听指定的端口,我的(为方便起见简化了)启动和停止服务器的代码如下:

    private bool state;
    private TcpListener listener;
    private CancellationTokenSource tokenSource;
    private Dictionary<string, ConnectedClient> clients;
    private List<Task> clientTasks;
    private ConnectedClient command_client;

    public async Task RunServer() {
        if (!state) {

            state = true;

            tokenSource = new CancellationTokenSource();
            listener = new TcpListener(IPAddress.Any, 55001);
            listener.Start();

            while (true) {
                try {
                    TcpClient socketClient = await listener.AcceptTcpClientAsync();
                    ConnectedClient client = new ConnectedClient(socketClient);
                    clients.Add(client.id, client);
                    client.task = ProcessClientAsync(client, tokenSource.Token);  
                    clientTasks.Add(client.task);


                }
                catch (ObjectDisposedException) {
                    //Server stopped by user
                    //exit while
                    break;
                }
            }

            /* Server has been stopped; close all connections */
            CloseAll();
        }
        else {
            /* Stop the server */
            tokenSource.Cancel();
            listener.Stop();
            /* Clean up of currently connected clients is handled in CloseAll, handled upon ObjectDisposedException above */
        }
    }

ConnectedClient 是一个 class 为了方便起见,我编写它来保存有关各个客户端的一些信息,并且具有处理接收数据时发生的事情的功能。我意识到我遗漏了一些东西以简化流程,但这段代码完全符合我的要求:服务器等待连接,创建一个 ConnectedClient 对象来处理接收到的连接,然后返回等待。当服务器已经在侦听时调用此函数时,侦听器将停止,这会导致侦听器抛出异常,从而打破循环并关闭所有连接。

当我试图创建一个侦听两个不同端口的服务器时出现问题,这两个端口需要区别对待。

这是我的代码(尝试):

    private bool state;
    private Dictionary<string, ConnectedClient> clients;
    private TcpListener command_listener;
    private TcpListener query_listener;
    private CancellationTokenSource tokenSource;
    private List<Task> clientTasks;

    public async Task RunServer() {
        if (!state) {

            state = true;

            tokenSource = new CancellationTokenSource();
            command_listener = new TcpListener(IPAddress.Any, 55001);
            command_listener.Start();
            query_listener = new TcpListener(IPAddress.Any, 55002);
            query_listener.Start();

            Task prevCommand = null;
            Task prevQuery = null;
            while (true) {
                try {

                    if (prevCommand == null || prevCommand.IsCompleted) {
                        prevCommand = waitForConnections(command_listener);
                    }

                    if (prevQuery == null || prevQuery.IsCompleted) {
                        prevQuery = waitForConnections(query_listener);
                    }
                    await Task.WhenAny(prevCommand, prevQuery);
                }
                catch (ObjectDisposedException) {
                    //Server stopped by user
                    //exit while
                    break;
                }
            }

            /* Server has been stopped; close all connections */
            CloseAll();
        }
        else {
            /* Stop the server */
            tokenSource.Cancel();
            command_listener.Stop();
            query_listener.Stop();
            /* Clean up of currently connected clients is handled in CloseAll, handled upon ObjectDisposedException above */
        }
    }

waitForConnections 的目的是处理连接请求,以便在一个端口上等待连接不会阻塞另一个端口上的连接,并确保只能在端口 55001 上建立一个连接。

    public async Task waitForConnections(TcpListener listener) {
        TcpClient socketClient = await listener.AcceptTcpClientAsync();

        if (((IPEndPoint)listener.LocalEndpoint).Port == 55001 ) {
            if (command_client == null) {
                command_client = new ConnectedClient(socketClient, onClientUpdate, onResend);

                clients.Add(command_client.id, command_client);
                command_client.task = ProcessClientAsync(command_client, tokenSource.Token);
                clientTasks.Add(command_client.task);
            }
            else {
                //only one client allowed on this port, reject the connection
                socketClient.Close();
            }
        }
        else {
            ConnectedClient client = new ConnectedClient(socketClient, onClientUpdate, onResend);

            clients.Add(client.id, client);
            client.task = ProcessClientAsync(client, tokenSource.Token);
            clientTasks.Add(client.task);
        }
    }

有了这个,我可以在两个端口上连接客户端而不会阻塞,但是再次调用这个函数并停止监听器似乎并没有像预期的那样抛出 ObjectDisposedException,这导致整个程序挂起什么都不做。我怀疑这是由于不负责任地使用异步函数引起的,但我该如何解决?

因为 await listener.AcceptTcpClientAsync() 调用是在异步任务内而不是直接在循环内,当侦听器停止时发生的异常导致任务 return 状态为"Faulted"。因为没有捕获到异常,循环继续,并且一个错误的任务被认为是一个已完成的任务,所以它会立即返回尝试监听连接,尽管监听器已停止(这反过来很可能导致任务再次出错) .

本可以通过检查错误任务而不是捕获异常来解决此问题,但我选择在尝试停止服务器时设置一个标志来打破循环并允许程序按预期关闭连接。