【问题标题】:Instantly detect client disconnection from server socket立即检测客户端与服务器套接字的断开连接
【发布时间】:2010-10-17 20:37:12
【问题描述】:

如何检测到客户端与我的服务器断开连接?

我的AcceptCallBack 方法中有以下代码

static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
  //Accept incoming connection
  Socket listener = (Socket)ar.AsyncState;
  handler = listener.EndAccept(ar);
}

我需要想办法尽快发现客户端已从handler Socket 断开。

我试过了:

  1. handler.Available;
  2. handler.Send(new byte[1], 0, SocketFlags.None);
  3. handler.Receive(new byte[1], 0, SocketFlags.None);

当您连接到服务器并想要检测服务器何时断开连接时,上述方法有效,但它们不起作用当您是服务器并想要检测客户端断开连接时。

任何帮助将不胜感激。

【问题讨论】:

标签: .net c# sockets tcp connection


【解决方案1】:

由于在套接字断开连接时没有可用于发出信号的事件,因此您必须以您可以接受的频率对其进行轮询。

使用此扩展方法,您可以有一个可靠的方法来检测套接字是否断开。

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}

【讨论】:

  • 这行得通。谢谢。我将方法更改为 return !(socket.Available == 0 && socket.Poll(1, SelectMode.SelectRead));因为我怀疑 socket.Available 比 Socket.Poll() 快
  • 除非连接的另一端实际关闭/关闭套接字,否则此方法不起作用。在超时期限之前不会注意到拔下的网络/电源线。立即通知断开连接的唯一方法是使用心跳功能来持续检查连接。
  • @Smart Alec:实际上,您应该使用上面显示的示例。如果更改顺序,可能会出现竞争条件:如果 socket.Available 返回 0 并且您在 socket.Poll 被调用之前收到一个数据包,Poll 将返回 true 并且方法将返回 false,尽管套接字实际上是仍然健康。
  • 这在 99% 的情况下都能正常工作,但有时它会产生错误的断开连接。
  • 就像 Matthew Finlay 注意到的那样,这有时会报告错误的断开连接,因为在 Poll 方法的结果和检查可用属性之间仍然存在竞争条件。一个数据包可能几乎准备好可以读取但还没有,因此可用为 0 - 但一毫秒后,有数据要读取。更好的选择是尝试接收一个字节和 SocketFlags.Peek 标志。或者实现某种形式的心跳并将连接状态保持在更高的级别。或者依靠您的发送/接收方法(和回调,如果使用异步版本)的错误处理。
【解决方案2】:

有人提到TCP Socket的keepAlive能力。 这里描述得很好:

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

我是这样使用它的:在套接字连接之后,我正在调用这个函数,它设置了 keepAlive。 keepAliveTime 参数指定超时,以毫秒为单位,在发送第一个保持活动数据包之前没有活动。 keepAliveInterval 参数指定在没有收到确认的情况下发送连续保持活动数据包之间的时间间隔,以毫秒为单位。

    void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval)
    {
        int size = Marshal.SizeOf(new uint());

        var inOptionValues = new byte[size * 3];

        BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
        BitConverter.GetBytes((uint)keepAliveTime).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2);

        socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
    }

我也在使用异步读取:

socket.BeginReceive(packet.dataBuffer, 0, 128,
                    SocketFlags.None, new AsyncCallback(OnDataReceived), packet);

在回调中,这里捕获到超时SocketException,当socket在keep-alive数据包后没有收到ACK信号时引发。

public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        SocketPacket theSockId = (SocketPacket)asyn.AsyncState;

        int iRx = socket.EndReceive(asyn);
    }
    catch (SocketException ex)
    {
        SocketExceptionCaught(ex);
    }
}

这样,我可以安全地检测 TCP 客户端和服务器之间的断开连接。

【讨论】:

  • 是的,将 keepalive+interval 设置为较低的值,任何轮询/发送都应该在 keepalive+10*interval 毫秒后失败。自 Vista 以来,10 次重试似乎是硬编码的?与大多数其他答案不同,即使您拔下电缆也能正常工作。这应该是公认的答案。
【解决方案3】:

这根本不可能。您和服务器之间没有物理连接(除了极少数情况下,您使用环回电缆在两台计算机之间进行连接)。

当连接正常关闭时,会通知对方。但是,如果连接以其他方式断开连接(例如用户连接被丢弃),那么服务器将不知道直到它超时(或尝试写入连接并且 ack 超时)。这就是 TCP 的工作方式,您必须接受它。

因此,“立即”是不现实的。您可以做的最好的事情是在超时期限内,这取决于代码运行的平台。

编辑: 如果您只是在寻找优雅的连接,那么为什么不从您的客户端向服务器发送“DISCONNECT”命令呢?

【讨论】:

  • 谢谢,但我实际上是指正常的软件断开连接,而不是物理断开连接。我正在运行 Windows
  • 如果他只是在寻找优雅的断开连接,他不需要发送任何东西。他的阅读将返回流的结尾。
【解决方案4】:

“这就是 TCP 的工作方式,你必须接受它。”

是的,你是对的。这是我已经意识到的生活事实。即使在使用此协议(甚至其他协议)的专业应用程序中,您也会看到相同的行为。我什至看到它出现在网络游戏中;你是好友说“再见”,他似乎又在线了 1-2 分钟,直到服务器“打扫房间”。

您可以在此处使用建议的方法,或者按照建议实施“心跳”。我选择前者。但如果我确实选择了后者,我只需让服务器每隔一段时间用一个字节“ping”每个客户端,看看我们是否有超时或没有响应。您甚至可以使用后台线程以精确的时间来实现这一点。如果您真的担心的话,甚至可以在某种选项列表(枚举标志或其他东西)中实现组合。但是,只要您进行更新,在更新服务器时有一点延迟并不是什么大不了的事。这是互联网,没有人会想到它是魔法! :)

【讨论】:

    【解决方案5】:

    在您的系统中实施心跳可能是一种解决方案。这只有在客户端和服务器都在您的控制之下时才有可能。您可以让 DateTime 对象跟踪从套接字接收到最后一个字节的时间。并假设在一定时间间隔内未响应的套接字丢失。这仅在您实施了心跳/自定义保持活动时才有效。

    【讨论】:

      【解决方案6】:

      我发现它非常有用,另一种解决方法!

      如果您使用异步方法从网络套接字读取数据(我的意思是,使用BeginReceive - EndReceive 方法),只要连接终止;出现以下情况之一:发送的消息没有数据(您可以通过Socket.Available 看到它 - 即使触发了BeginReceive,它的值也将为零)或Socket.Connected 在此调用中值变为假(不要'然后不要尝试使用EndReceive)。

      我发布了我使用的函数,我想你可以更好地理解我的意思:


      private void OnRecieve(IAsyncResult parameter) 
      {
          Socket sock = (Socket)parameter.AsyncState;
          if(!sock.Connected || sock.Available == 0)
          {
              // Connection is terminated, either by force or willingly
              return;
          }
      
          sock.EndReceive(parameter);
          sock.BeginReceive(..., ... , ... , ..., new AsyncCallback(OnRecieve), sock);
      
          // To handle further commands sent by client.
          // "..." zones might change in your code.
      }
      

      【讨论】:

        【解决方案7】:

        这对我有用,关键是您需要一个单独的线程来通过轮询来分析套接字状态。在与套接字失败检测相同的线程中执行此操作。

        //open or receive a server socket - TODO your code here
        socket = new Socket(....);
        
        //enable the keep alive so we can detect closure
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
        
        //create a thread that checks every 5 seconds if the socket is still connected. TODO add your thread starting code
        void MonitorSocketsForClosureWorker() {
            DateTime nextCheckTime = DateTime.Now.AddSeconds(5);
        
            while (!exitSystem) {
                if (nextCheckTime < DateTime.Now) {
                    try {
                        if (socket!=null) {
                            if(socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) {
                                //socket not connected, close it if it's still running
                                socket.Close();
                                socket = null;    
                            } else {
                                //socket still connected
                            }    
                       }
                   } catch {
                       socket.Close();
                    } finally {
                        nextCheckTime = DateTime.Now.AddSeconds(5);
                    }
                }
                Thread.Sleep(1000);
            }
        }
        

        【讨论】:

          【解决方案8】:

          这里的示例代码 http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx 展示了如何在不发送任何数据的情况下确定 Socket 是否仍处于连接状态。

          如果您在服务器程序上调用了 Socket.BeginReceive(),然后客户端“优雅地”关闭了连接,您的接收回调将被调用并且 EndReceive() 将返回 0 字节。这些 0 字节意味着客户端“可能”已断开连接。然后,您可以使用 MSDN 示例代码中显示的技术来确定连接是否已关闭。

          【讨论】:

            【解决方案9】:

            通过mbargielmycelo在接受的答案上扩展cmets,以下可以与服务器端的非阻塞套接字一起使用,以通知客户端是否已关闭。

            这种方法不会受到影响接受答案中 Poll 方法的竞争条件。

            // Determines whether the remote end has called Shutdown
            public bool HasRemoteEndShutDown
            {
                get
                {
                    try
                    {
                        int bytesRead = socket.Receive(new byte[1], SocketFlags.Peek);
            
                        if (bytesRead == 0)
                            return true;
                    }
                    catch
                    {
                        // For a non-blocking socket, a SocketException with 
                        // code 10035 (WSAEWOULDBLOCK) indicates no data available.
                    }
            
                    return false;
                }
            }
            

            该方法基于这样一个事实,即Socket.Receive 方法在远程端关闭其套接字并且我们已从中读取所有数据后立即返回零。来自Socket.Receive documentation

            如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且已经接收到所有可用数据,则 Receive 方法将立即完成并返回零字节。

            如果您处于非阻塞模式,并且协议栈缓冲区中没有可用数据,则 Receive 方法将立即完成并抛出 SocketException。

            第二点解释了 try-catch 的必要性。

            使用SocketFlags.Peek 标志不会触及任何接收到的数据,以供单独的接收机制读取。

            上述内容也适用于 blocking 套接字,但请注意代码将在 Receive 调用上阻塞(直到接收到数据或接收超时,再次导致 @987654327 @)。

            【讨论】:

              【解决方案10】:

              你不能只使用 Select 吗?

              在已连接的套接字上使用 select。如果选择返回您的套接字为就绪,但随后的接收返回 0 字节,这意味着客户端断开连接。 AFAIK,这是确定客户端是否断开连接的最快方法。

              我不懂 C#,所以如果我的解决方案不适合 C#(尽管 C# 确实提供了 select)或者我误解了上下文,请忽略。

              【讨论】:

                【解决方案11】:

                使用 SetSocketOption 方法,您将能够设置 KeepAlive,以便在 Socket 断开连接时通知您

                Socket _connectedSocket = this._sSocketEscucha.EndAccept(asyn);
                                _connectedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
                

                http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx

                希望对您有所帮助! 拉米罗·里纳尔迪

                【讨论】:

                • 它不会“让您知道任何时候套接字断开连接”。如果保活计时器到期,它最终会检测它。默认情况下,它设置为两个小时。您不能将其描述为“无论何时”。您也不能将其描述为“让[ting]您知道:您仍然必须进行读取或写入才能检测到故障。 -1
                【解决方案12】:

                我有同样的问题,试试这个:

                void client_handler(Socket client) // set 'KeepAlive' true
                {
                    while (true)
                    {
                        try
                        {
                            if (client.Connected)
                            {
                
                            }
                            else
                            { // client disconnected
                                break;
                            }
                        }
                        catch (Exception)
                        {
                            client.Poll(4000, SelectMode.SelectRead);// try to get state
                        }
                    }
                }
                

                【讨论】:

                  【解决方案13】:

                  这是在 VB 中,但它似乎对我很有效。它像上一篇文章一样寻找一个 0 字节的返回值。

                  Private Sub RecData(ByVal AR As IAsyncResult)
                      Dim Socket As Socket = AR.AsyncState
                  
                      If Socket.Connected = False And Socket.Available = False Then
                          Debug.Print("Detected Disconnected Socket - " + Socket.RemoteEndPoint.ToString)
                          Exit Sub
                      End If
                      Dim BytesRead As Int32 = Socket.EndReceive(AR)
                      If BytesRead = 0 Then
                          Debug.Print("Detected Disconnected Socket - Bytes Read = 0 - " + Socket.RemoteEndPoint.ToString)
                          UpdateText("Client " + Socket.RemoteEndPoint.ToString + " has disconnected from Server.")
                          Socket.Close()
                          Exit Sub
                      End If
                      Dim msg As String = System.Text.ASCIIEncoding.ASCII.GetString(ByteData)
                      Erase ByteData
                      ReDim ByteData(1024)
                      ClientSocket.BeginReceive(ByteData, 0, ByteData.Length, SocketFlags.None, New AsyncCallback(AddressOf RecData), ClientSocket)
                      UpdateText(msg)
                  End Sub
                  

                  【讨论】:

                    【解决方案14】:

                    以上答案可以总结如下:

                    Socket.Connected 属性根据上次读取或接收状态确定套接字状态,因此在您手动关闭连接或远程结束正常关闭套接字(关闭)之前,它无法检测当前断开状态。

                    所以我们可以使用下面的函数来检查连接状态:

                       bool IsConnected(Socket socket)
                        {
                            try
                            {
                                if (socket == null) return false;
                                return !((socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) || !socket.Connected);
                            }
                            catch (SocketException)
                            {
                                return false;
                            }
                    
                            //the above code is short exp to :
                            /* try
                             {
                                 bool state1 = socket.Poll(5000, SelectMode.SelectRead);
                                 bool state2 = (socket.Available == 0);
                                 if ((state1 && state2) || !socket.Connected)
                                     return false;
                                 else
                                     return true;
                             }
                             catch (SocketException)
                             {
                                 return false;
                             }
                            */
                        }
                    

                    上面的检查也需要关心poll响应时间(block time) 也正如 Microsoft Documents 所说:这种轮询方法“无法检测到诸如网络电缆损坏或远程主机被不正常关闭之类的问题”。 同样如上所述,socket.poll 和 socket.avaiable 之间存在竞争条件,这可能会导致错误断开连接。

                    Microsoft Documents 所说的最佳方法是尝试发送或接收数据以检测 MS 文档所说的这些类型的错误。 以下代码来自微软文档:

                     // This is how you can determine whether a socket is still connected.
                     bool IsConnected(Socket client)
                     {
                        bool blockingState = client.Blocking; //save socket blocking state.
                        bool isConnected = true;
                        try
                        {
                           byte [] tmp = new byte[1]; 
                           client.Blocking = false;
                           client.Send(tmp, 0, 0); //make a nonblocking, zero-byte Send call (dummy)
                           //Console.WriteLine("Connected!");
                        }
                        catch (SocketException e)
                        {
                           // 10035 == WSAEWOULDBLOCK
                           if (e.NativeErrorCode.Equals(10035))
                           {
                               //Console.WriteLine("Still Connected, but the Send would block");
                           }
                           else
                           {
                               //Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode);
                               isConnected  = false;
                           }
                       }
                       finally
                       {
                           client.Blocking = blockingState;
                       }
                       //Console.WriteLine("Connected: {0}", client.Connected);
                       return isConnected  ;
                     }
                    

                    //还有来自微软文档的 cmets*

                    • socket.Connected 属性获取 Socket 在最后一次 I/O 操作时的连接状态。当它返回 false 时,Socket 要么从未连接,要么不再连接。
                    • Connected 不是线程安全的;当 Socket 与另一个线程断开连接时,它可能会在操作中止后返回 true。
                    • Connected 属性的值反映了最近一次操作时的连接状态。
                    • 如果您需要确定连接的当前状态,请进行非阻塞、零字节的发送调用。如果调用成功返回或抛出 WAEWOULDBLOCK 错误代码(10035),则套接字仍处于连接状态; //否则,套接字不再连接。

                    【讨论】:

                      【解决方案15】:

                      如果要轮询,还可以检查套接字的 .IsConnected 属性。

                      【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多