87

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

我的AcceptCallBack方法中有以下代码

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

我需要找到一种方法来尽快发现客户端已与handlerSocket 断开连接。

我试过了:

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

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

任何帮助将不胜感激。

4

15 回答 15

121

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

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

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}
于 2009-04-06T16:46:22.707 回答
28

有人提到 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);

在回调中,这里被捕获 timeout SocketException,当套接字在保持活动数据包后没有收到 ACK 信号时引发。

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

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

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

于 2015-08-27T08:42:46.530 回答
16

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

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

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

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

于 2009-04-06T16:45:53.597 回答
6

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

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

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

于 2009-11-07T10:57:24.900 回答
3

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

于 2009-04-06T17:06:30.007 回答
2

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

如果您使用异步方法从网络套接字读取数据(我的意思是,使用BeginReceive-EndReceive 方法),每当连接终止时;出现以下情况之一:发送的消息没有数据(您可以看到它Socket.Available- 即使BeginReceive被触发,它的值也将为零),或者Socket.Connected在此调用中值变为 false(然后不要尝试使用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.
}
于 2010-01-06T19:18:49.913 回答
2

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

//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);
    }
}
于 2015-07-28T03:08:33.077 回答
1

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

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

于 2011-01-02T03:36:28.287 回答
1

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

这种方法不会受到影响接受答案中 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 文档

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

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

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

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

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

于 2018-11-15T09:09:25.910 回答
0

你不能只使用选择吗?

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

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

于 2009-06-30T13:50:29.793 回答
0

使用 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

希望能帮助到你!拉米罗·里纳尔迪

于 2011-01-05T13:24:38.627 回答
0

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

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
        }
    }
}
于 2018-11-26T20:46:46.497 回答
0

这是在 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
于 2020-02-19T20:40:25.247 回答
0

以上答案可归纳如下:

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;
         }
        */
    }

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

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

 // 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  ;
 }

//以及来自微软文档的评论*

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

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

于 2009-04-06T16:51:09.230 回答