检测TCP侦听器何时关闭时的问题

时间:2017-05-12 19:06:59

标签: c# asynchronous tcp deadlock

我正在创建自定义TCP / IP客户端/服务器应用程序,并且在尝试停止服务器时遇到了麻烦。最初,我的代码使用一个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是我编写的一个类,它为了方便起见保存了一些有关各个客户端的信息,并且具有处理收到数据时会发生什么的函数。我意识到我留下了一些简化的东西,但是这段代码完全符合我的要求:服务器等待连接,创建一个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,这会导致整个程序挂起而不会做任何事情。我怀疑这是由于一些不负责任的使用异步函数引起的,但我该如何解决呢?

1 个答案:

答案 0 :(得分:0)

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

可以通过检查故障任务而不是捕获异常来修复此问题,但是当我尝试停止破坏循环的服务器并允许程序按预期关闭连接时,我选择设置标志。

相关问题