立即检测客户端与服务器套接字的断开连接


80

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

我有下面的代码在我的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);

当您连接到服务器并想检测服务器何时断开连接时,上述方法可以工作,但是当您是服务器并想检测客户端断开连接时,上述方法不起作用

任何帮助将不胜感激。


10
@Samuel:TCP和连接标签非常相关的这篇文章在TCP保持连接(而其他网络协议如UDP没有)。
Noldorin

3
有关我的博客的心跳解决方案的更多信息:半开(断开)连接的检测
Stephen Cleary,2010年

这里描述的解决方案对我来说很好用:stackoverflow.com/questions/1387459/…–
Rawk

Answers:


109

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

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

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

1
这工作了。谢谢。我更改了返回方法!(socket.Available == 0 && socket.Poll(1,SelectMode.SelectRead)); 因为我怀疑socket.Available比Socket.Poll()更快

27
除非连接的另一端实际上关闭/关闭套接字,否则此方法不起作用。在超时期限之前,不会注意到网络/电源线未拔出。立即通知断开连接的唯一方法是使用心跳功能来连续检查连接。
卡巴斯尔·霍尔顿

7
@Smart Alec:实际上,您应该使用上面显示的示例。如果更改顺序,则有潜在的竞争条件:如果socket.Available返回0且在socket.Poll调用之前收到一个数据包,Poll则返回true,方法将返回false,尽管套接字实际上仍然健康。
Groo 2010年

4
这种方法在99%的时间内都能正常工作,但有时会出现错误的断开连接。
马修·芬利2013年

5
就像Matthew Finlay注意到的那样,有时会报告错误的断开连接,因为在Poll方法的结果与检查Available属性之间仍然存在竞争条件。一个数据包几乎可以读取了,但尚未准备好,因此“可用”为0-但在一毫秒后,还有要读取的数据。更好的选择是尝试接收一个字节和SocketFlags.Peek标志。或实施某种形式的心跳,并将连接状态保持在较高级别。或者依靠发送/接收方法(&回调,如果使用异步版本)上的错误处理。
mbargiel 2015年

22

有人提到TCP套接字的keepAlive功能。这里很好地描述了:

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

我以这种方式使用它:连接套接字后,我将调用此函数,该函数将keepAlive设置为on。该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)time).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);

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

我也在使用同步阅读:

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

在回调中,这里捕获了超时SocketException,当保持活动数据包后套接字没有收到ACK信号时,超时会增加。

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

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

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


6
是的,请将keepalive + interval设置为一个较低的值,在keepalive + 10 * interval毫秒后,任何轮询/发送都将失败。自Vista以来,这10次重试似乎是硬编码的?即使您不像大多数其他答案一样拔出电缆,其工作原理也是如此。这应该是公认的答案。
toster-cx

15

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

正常关闭连接后,将通知另一端。但是,如果以其他方式断开连接(例如断开用户连接),则服务器直到超时(或尝试写入连接且ack超时)才知道。这只是TCP的工作方式,您必须忍受它。

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

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


1
谢谢,但我实际上是指正常的软件断开连接,而不是物理断开连接。我正在运行Windows

1
如果他只是在寻找正常的断开连接,则不必发送任何内容。他的阅读将返回流的结尾。
罗恩侯爵

6

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

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

您可以在此处使用建议的方法,也可以实施“心跳”。我选择前者。但是,如果我确实选择了后者,则只需让服务器每隔一个字节就“ ping”每个客户端一次,看看是否有超时或没有响应。您甚至可以使用后台线程以精确的时间来实现这一目标。如果您真的很担心,甚至可以在某种选项列表(枚举标志等)中实现组合。但是,只要您确实进行更新,就没有一点延迟可以更新服务器了。这是互联网,没有人期望它是神奇的!:)


3

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


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.
}

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

1

此处的示例代码 http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connected.aspx 显示了如何确定Socket是否仍保持连接而不发送任何数据。

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


1

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

这种方法不会遇到会影响已接受答案中“轮询”方法的竞争条件。

// 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调用上阻塞(直到接收到数据或接收超时结束,再次导致SocketException)。


0

您不能只使用Select吗?

在连接的插座上使用选择。如果选择返回,且套接字为“就绪”,但随后的接收返回0个字节,则表示客户端断开了连接。AFAIK,这是确定客户端是否断开连接的最快方法。

我不了解C#,因此请忽略是否我的解决方案不适合C#(尽管C#确实提供了选择)或是否我误解了上下文。


0

使用SetSocketOption方法,您将能够设置KeepAlive,当套接字断开连接时,它将通知您

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

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

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


2
它不会“让您知道套接字何时断开连接”。它会检测到它,最终,如果保活定时器超时。默认情况下,它设置为两个小时。您不能将其描述为“任何时候”。您也不能将其描述为“让您知道”:您仍然必须进行读取或写入才能检测到故障。-1
洛恩侯爵

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.