【问题标题】:Issue detecting when TCP listener has been closed检测 TCP 侦听器何时关闭的问题
【发布时间】:2017-05-12 19:06:58
【问题描述】:

我正在创建一个自定义 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 按预期抛出,这会导致整个程序挂起而不是做任何事情。我怀疑这是由于不负责任地使用异步函数造成的,但我该如何解决呢?

【问题讨论】:

  • 您打算强制(和滥用)ObjectDisposedExceptions 以指示某种形式的连接终止的方法在我看来完全错误。无论如何,停止 TcpListener 对象并不会释放它。
  • 公平,我认为使用异常来跟踪状态是不好的做法。就处置而言,为了简洁起见,我将其省略了,因为我认为它与问题无关。我想我真正的问题是,为什么在第一种情况下会抛出异常,而在第二种情况下却没有?
  • 对不起,我还是不清楚。为什么你不能读懂我的心思? ;) 在第一种情况下,它只使用 TcpListener.Stop() 调用抛出了 ObjectDisposedException,但在第二种情况下,它没有。如果停止侦听器不会释放它,那为什么它首先会起作用?
  • 不,不,我想我会慢慢明白你想做什么。所以基本上,你第一次调用RunServer(),不知何故抛出了一个ObjectDisposedException。然后再次调用RunServer(),而state 仍然为真,这将导致调用TcpListner.Stop() 方法。你也期望 Stop() 抛出,对吧?
  • 是的,没错。但是直到调用 TcpListener.Stop( ) 才抛出异常,该异常被捕获并打破负责侦听连接的无限 while 循环。也许 Stop() 会导致一些其他对象被释放,这就是引发异常的原因?

标签: c# asynchronous tcp deadlock


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 2021-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多