【问题标题】:.Net Socket doesn't respond to remote disconnect?.Net Socket 不响应远程断开连接?
【发布时间】:2012-03-29 19:08:05
【问题描述】:

我正在编写一个小型 (C#) 客户端应用程序,它使用 TCP/IP 连接将数据发送到远程服务器。我正在使用标准的 .Net TcpClient 对象并希望从客户端保持连接打开,因为我定期向服务器提交数据包。但是,服务器可能会关闭连接,在这种情况下,我需要知道在发送下一个数据包之前重新连接。

使用 Wireshark,我可以(仅)在服务器终止连接时看到以下对话框:

server >>> FIN, ACK
                ACK <<< client

没有看到的是我的客户端用它自己的 FIN 响应,以完成连接关闭。结果是我的客户端程序在发送下一个数据包后才发现连接断开了。

有什么方法可以设置 TcpClient 或其底层 Socket 以完成断开连接,并提供一些反馈,以便我的客户端代码知道在发送下一个数据包之前重新连接?

添加以回应以下评论: 我的发送代码非常简单 - 维护 TcpClient 和 NetworkStream 成员变量的对象,有一个成员函数(基本上)包含以下内容:

bool sent = false;
byte[] buffer = Encoding.UTF8.GetBytes(dataString);
while (!sent)
{
    try
    {
        m_outStream.Write(buffer, 0, buffer.Length);
        sent = true;
    }
    catch (Exception ex)
    {
        if (m_outStream != null) { m_outStream.Dispose(); }
        m_client = new TcpClient(AddressFamily.InterNetwork);
        m_client.Connect(ipAddress, ipPort);
        m_outStream = m_client.GetStream();
    }
}

在 m_client 和 m_outStream 初始化后,每次只执行一次。然后使用 Wireshark,我可以看到服务器发送一个带有标志 FIN, ACK 的数据包,客户端用 ACK 响应该数据包。

下次我调用我的函数时,数据以PSH, ACK 发送出去,服务器以RST, ACK 响应但不读取传入的数据。客户端不会引发异常。

然后我再次调用我的函数,并引发异常导致重新启动连接。

【问题讨论】:

    标签: c# .net sockets tcpclient


    【解决方案1】:

    一般来说,您应该能够在TcpCient 实例上使用Connected 属性:

    请看这里:
    http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.connected.aspx

    但是:

    因为 Connected 属性只反映了 从最近的操作开始,您应该尝试发送 或接收消息以确定当前状态。留言后 发送失败,此属性不再返回 true。请注意,这 行为是设计使然。您无法可靠地测试 连接,因为在测试和发送/接收之间的时间内, 连接可能已丢失。您的代码应该假设 套接字已连接,并优雅地处理失败的传输。

    尝试以下操作以确保 Connected 标志保持最新状态:

    var tcpClient = new TcpClient ();
    tcpClient.Connect();
    
    var stream = tcpClient.GetStream();
    
    // buffer size need to be > 0
    int[] buffer = new int[1];
    stream.Read(buffer, 0, 0);
    
    if(!tcpClient.Connected)
        // do something
    

    基于反编译,应该可以从流中读取0 字节,至少.NET Framework TcpClient 中没有检查可以防止这种情况发生。但是,在从框架调用以实际从网络流中读取的外部代码中,它可能不会大声。

    请务必在完成后对TcpClientStream 进行Dispose,处置TcpClient 不会处置Stream,因此您需要手动执行此操作,然后释放所有资源(GC之后)。

    【讨论】:

    • 感谢您的评论,但文档指出 Connected 仅在最后一次读/写时为真。我担心的是 TcpClient 使连接处于半废弃状态。
    • 我已经更新了我的答案,让您知道如何解决这个问题。但总的来说,框架处理 Tcp Connections 的方式是假设你假设它是打开的并且在它们不是时优雅地处理。您还负责清理TcpClientStream,如上一段所述。
    • 非常感谢您的更新 - 我猜stream.Read 调用与我发现的 Socket 调用基本相同,但抽象级别更高。
    • 高级代码实际上存在问题 - NetworkStream.Read 即使在读取零字节时似乎也会阻塞,至少在新打开的连接和流上是这样,而低级 Socket 调用确实如此在相同情况下不阻塞。
    • 谢谢,很高兴知道!不过,您可以设置读取超时,例如一个非常小的超时,这样您可以在短时间内停止流阻塞。
    【解决方案2】:

    我找到了解决当前问题的部分答案。

    虽然我仍然不知道是否可以让我的 TcpClient 完成断开连接,但我可以使用以下代码可靠地发现套接字是否已断开连接:

    if (m_client.Client.Poll(1000, SelectMode.SelectRead) 
    && (m_client.Client.Available == 0))
    {
        // Connection has gone - reconnect!
        m_client = new TcpClient(AddressFamily.InterNetwork);
        m_client.Connect(ipAddress, ipPort);
    }
    else
    {
        // Connection is good, nothing to do
    }
    

    【讨论】:

    • 很好,请注意:最好的做法是假设它是打开的,而不是专注于检测连接是否打开,而是准备代码以优雅地处理异常,因为没有办法告诉它是否在您的支票和您发送/接收数据之间被关闭,所以无论如何您都需要处理这些异常。
    • 是的,我的代码设置为处理异常。我的问题是,在连接仅半关闭的情况下,我的第一个数据包 is 仅被使用 RST 标志的服务器拒绝。但是,这不足以引发异常。只有当我next尝试发送数据包时才会发生异常,此时我已经丢失了前面的数据包。
    • 好吧,这听起来你做错了什么,肯定有异常,你能发布你的代码吗?
    【解决方案3】:

    来自 MSDN TcpClient.Connected 属性:
    类型:System.Boolean
    如果客户端套接字在最近一次操作时连接到远程资源,则为 true;否则为假。

    这意味着,您必须向服务器发送一些数据来检测断开的连接。读取不起作用,因为您从缓冲区中读取。

    查看我对相关问题的回答 (https://stackoverflow.com/a/25680975/2505186),
    链接其他人的答案,其中描述了一种合适的方式来检测连接状态: How to check if TcpClient Connection is closed?

    对您很重要:
    当服务器这样做时,客户端不会自动关闭连接。连接在客户端处于 CLOSE_WAIT 状态,在服务器端处于 FIN_WAIT2 状态。请参阅维基百科文章 Transmission Control Protocol 中的相关部分。使用上面链接答案中的代码,您可以检测到连接即将关闭。此外,您可以完成关闭过程并在需要时重新打开它。

    【讨论】:

      【解决方案4】:

      我用来检测连接状态的方法就是这个。

      static class SocketExtensions
          {
              /// <summary>
              /// Extension method to tell if the Socket REALLY is closed
              /// </summary>
              /// <param name="socket"></param>
              /// <returns></returns>
              public static bool IsConnected(this Socket socket)
              {
                  try
                  {
                      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
                  }
                  catch (SocketException) { return false; }
              }
          }
      

      当我想关闭连接时,我调用以下命令。关闭底层流,然后关闭顶部的客户端对象。 我将它包含在尝试和捕获中,以确保尝试关闭它们。 注意:本例中的 PeerStream 是 NetworkStream(来自 Client.GetStream())

      /// <summary>
              /// Method will disconnect this peer forcefully
              /// </summary>
              public void Disconnect()
              {
                  try
                  {
                      PeerStream.Close();
                  }
                  catch (Exception ee)
                  {
                  }
                  try
                  {
                      _client.Client.Disconnect(false);
                  }
                  catch (Exception ee)
                  {
      
                  }
              }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-09-20
        • 2022-07-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多