【问题标题】:Problems with closing TcpClient and NetworkStream关闭 TcpClient 和 NetworkStream 的问题
【发布时间】:2016-03-18 18:52:32
【问题描述】:

目前我正在开发一个简单的客户端服务器聊天程序(想在 C# 中进行客户端服务器通信)。到目前为止,除了与服务器正确断开连接外,它仍然有效。

在客户端我在这里使用这段代码来关闭连接:

client.Client.Disconnect(false); // client is the TcpClient
client.Close();

在服务器上有一个线程循环等待来自客户端的消息:

private void StartChat()
{
    int requestCount = 0;
    byte[] bytesFrom = new byte[10025];
    string dataFromClient = null;
    string rCount = null;

    while (true)
    {
        try
        {
            requestCount++;

            NetworkStream stream = tcpClient.GetStream();

            int bufferSize = (int)tcpClient.ReceiveBufferSize;
            if (bufferSize > bytesFrom.Length)
            {
                bufferSize = bytesFrom.Length;
            }

            stream.Read(bytesFrom, 0, bufferSize);
            dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom);
            dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"));
            rCount = Convert.ToString(requestCount);

            string message = client.Name + " says: " + dataFromClient;
            program.Broadcast(message);

        }
        catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException)
        { 
            program.UserDisconnected(client);
            break;
        }
        catch(ArgumentOutOfRangeException ex)
        {
            Debug.WriteLine(ex.ToString());
            break;
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            break;
        }
    }

如果客户端与上面显示的代码断开连接,则该函数一直在获取流并产生这样的输出:

\0\0\0\0\0\0\0 [and so on]

在这种情况下,ArgumentOutOfRangeException 将被抛出,因为没有$ 的索引。我添加了一个break 以避免和无休止地执行循环。

令人惊讶的是,ObjectDisposedException 不会被抛出。此外,System.IO.IOException 也不会被抛出,但它应该被抛出,因为流已关闭,因此连接被拒绝。

如果我只是关闭连接到服务器的客户端应用程序,服务器不会停止循环,它只是等待一个永远不会出现的流,因为客户端已断开连接。

那么我如何检测客户端是否已断开连接或不再可访问?我关闭客户端连接的方式是关闭连接的正确方式吗?

感谢您的帮助!

更新:

private void StartChat()
{
    int requestCount = 0;
    byte[] bytesFrom = new byte[10025];
    string dataFromClient = null;
    string rCount = null;

    while (true)
    {
        try
        {
            requestCount++;

            NetworkStream stream = tcpClient.GetStream();
            stream.ReadTimeout = 4000;

            int bufferSize = (int)tcpClient.ReceiveBufferSize;
            if (bufferSize > bytesFrom.Length)
            {
                bufferSize = bytesFrom.Length;
            }


            // Wait for a client message. If no message is recieved within the ReadTimeout a IOException will be thrown
            try
            {
                int bytesRead = stream.Read(bytesFrom, 0, bufferSize);
                stream.Flush();

                if (bytesRead == 0)
                {
                    throw new System.IO.IOException("Connection seems to be refused or closed.");
                }
            }
            catch (System.IO.IOException)
            {
                byte[] ping = System.Text.Encoding.UTF8.GetBytes("%");
                stream.WriteTimeout = 1;

                stream.Write(ping, 0, ping.Length);
                continue;
            }


            dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
            dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"));
            rCount = Convert.ToString(requestCount);

            string message = client.Name + " says: " + dataFromClient;
            program.Broadcast(message);

        }
        catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException)
        {
            Debug.WriteLine(ex.ToString());
            program.UserDisconnected(client);
            break;
        }
        catch(ArgumentOutOfRangeException ex)
        {
            Debug.WriteLine(ex.ToString());
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            break;
        }
    }
}

【问题讨论】:

    标签: c# tcpclient networkstream


    【解决方案1】:

    您应该检查stream.Read 的返回值 - 它返回实际读取的字节数。

    当客户端断开连接时,该值为 0,当它读取数据时,读取的字节数通常小于缓冲区大小。

    int bytes = stream.Read(bytesFrom, 0, bufferSize);
    if (bytes == 0)
    {
        // client has disconnected
        break;
    }
    
    dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom, 0, bytes);
    

    针对您最近的评论,当客户端终止其连接时,可能会发生三件事:

    • 客户端正确关闭连接,你收到0字节
    • 发生了可检测到的事情,导致异常
    • 客户端“消失”,但没有信号发送到您的端

    在最后一种情况下,服务器仍然会像存在活动连接一样工作,直到它尝试发送数据,这显然会失败。这就是为什么许多协议使用连接超时和/或保持活动机制的原因。

    【讨论】:

    • 这是否意味着您无法检测到客户端是否已经离开?查看文档,它应该抛出一个 ObjectDispoedException 或至少一个 IOException,因为它无法再从中读取
    • A graceful 断开连接不是例外情况,stream.Read() 返回 0 检测到。您确实应该有一个异常处理程序,但我怀疑ObjectDisposedException 会抛出,除非你自己处理它然后尝试使用它。
    • 你是对的,除非该对象仍然存在并且未被垃圾收集器删除或手动处置,否则不会抛出 DisposedException。但是,非常感谢,这有效!我会尽快接受你的回答。编辑:如果我关闭客户端,循环不会结束,并且会一直持续到服务器应用程序关闭。我该如何解决这个问题?
    • @chris579 回答了我回答中的最后一个问题。
    • 前两种情况已经处理,但第三种情况导致atm出现问题。我更新了代码现在的样子。尽管客户端已经离开,但第二次流写入成功。我真的不知道如何解决这种行为。我已经尝试向客户端发送一个小包,该客户端将发送一个小包,但即使客户端不在,服务器也可以从流中读取并获取一个无法发送的包,因为客户端已经不在。
    【解决方案2】:

    int bytesRead = stream.Read(...); if (bytesRead == 0) // 客户端断开

    【讨论】:

    • 嗯,你要慢。看看上面的答案;)
    • 是的...我知道这里有足够多的开发人员,也许该网站可以让人们知道何时有新的评论/答案...并在他们使用时修复分页:p
    • 是的 :) 还有一个问题悬而未决,只需查看我在第一个答案中的评论即可。也许你可以帮我解决这个问题;)
    • 我看了,没发现问题。 (?)
    • 我想不出如何雄辩地表达,所以,这就是野兽的本性......你告诉它等到它收到一条消息,该死的它会:p你可能可以将“读取超时”设置为更短 - 没有*方法来检测其中一个对等方是否只是“走开”。 ..另一个选项是发送一个“心跳”,以验证它是否仍然存在(如果不是,它将超时/失败)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-02
    • 1970-01-01
    • 2011-06-21
    • 2017-04-09
    • 2011-02-01
    • 1970-01-01
    相关资源
    最近更新 更多