简体   繁体   English

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

[英]Instantly detect client disconnection from server socket

How can I detect that a client has disconnected from my server?如何检测客户端与我的服务器断开连接?

I have the following code in my AcceptCallBack method我的AcceptCallBack方法中有以下代码

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

I need to find a way to discover as soon as possible that the client has disconnected from the handler Socket.我需要找到一种方法来尽快发现客户端已与handler Socket 断开连接。

I've tried:我试过了:

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

The above approaches work when you are connecting to a server and want to detect when the server disconnects but they do not work when you are the server and want to detect client disconnection.当您连接到服务器并希望检测服务器何时断开连接时,上述方法有效,但当您是服务器并希望检测客户端断开连接时,它们不起作用

Any help will be appreciated.任何帮助将不胜感激。

Since there are no events available to signal when the socket is disconnected, you will have to poll it at a frequency that is acceptable to you.由于在套接字断开连接时没有可用于发出信号的事件,因此您必须以您可以接受的频率对其进行轮询。

Using this extension method, you can have a reliable method to detect if a socket is disconnected.使用这种扩展方法,您可以有一种可靠的方法来检测套接字是否断开连接。

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

Someone mentioned keepAlive capability of TCP Socket.有人提到TCP Socket的keepAlive能力。 Here it is nicely described:这里很好地描述了它:

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

I'm using it this way: after the socket is connected, I'm calling this function, which sets keepAlive on.我是这样使用它的:在连接套接字后,我正在调用这个函数,它设置了 keepAlive。 The keepAliveTime parameter specifies the timeout, in milliseconds, with no activity until the first keep-alive packet is sent. keepAliveTime参数指定超时(以毫秒为单位),在发送第一个保持活动数据包之前没有活动。 The keepAliveInterval parameter specifies the interval, in milliseconds, between when successive keep-alive packets are sent if no acknowledgement is received. 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);
    }

I'm also using asynchronous reading:我也在使用异步阅读:

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

And in callback, here is caught timeout SocketException , which raises when socket doesn't get ACK signal after keep-alive packet.在回调中,这里捕获超时SocketException ,当套接字在保持活动数据包后没有收到 ACK 信号时引发。

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

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

This way, I'm able to safely detect disconnection between TCP client and server.这样,我就可以安全地检测 TCP 客户端和服务器之间的断开连接。

This is simply not possible.这根本不可能。 There is no physical connection between you and the server (except in the extremely rare case where you are connecting between two compuers with a loopback cable).您和服务器之间没有物理连接(除非在极少数情况下您使用环回电缆连接两台计算机)。

When the connection is closed gracefully, the other side is notified.当连接正常关闭时,通知另一方。 But if the connection is disconnected some other way (say the users connection is dropped) then the server won't know until it times out (or tries to write to the connection and the ack times out).但是,如果连接以其他方式断开(假设用户连接断开),那么服务器将不知道,直到它超时(或尝试写入连接并且 ack 超时)。 That's just the way TCP works and you have to live with it.这就是 TCP 的工作方式,您必须接受它。

Therefore, "instantly" is unrealistic.因此,“立即”是不现实的。 The best you can do is within the timeout period, which depends on the platform the code is running on.您能做的最好的事情是在超时期限内,这取决于运行代码的平台。

EDIT: If you are only looking for graceful connections, then why not just send a "DISCONNECT" command to the server from your client?编辑:如果你只是在寻找优雅的连接,那么为什么不从你的客户端向服务器发送一个“DISCONNECT”命令呢?

"That's just the way TCP works and you have to live with it." “这就是 TCP 的工作方式,你必须接受它。”

Yup, you're right.是的,你说得对。 It's a fact of life I've come to realize.这是我逐渐意识到的生活事实。 You will see the same behavior exhibited even in professional applications utilizing this protocol (and even others).即使在使用此协议(甚至其他协议)的专业应用程序中,您也会看到相同的行为。 I've even seen it occur in online games;我什至看到它发生在网络游戏中; you're buddy says "goodbye", and he appears to be online for another 1-2 minutes until the server "cleans house".你的好友说“再见”,他似乎又在线了 1-2 分钟,直到服务器“打扫房间”。

You can use the suggested methods here, or implement a "heartbeat", as also suggested.您可以在此处使用建议的方法,也可以按照建议实施“心跳”。 I choose the former.我选择前者。 But if I did choose the latter, I'd simply have the server "ping" each client every so often with a single byte, and see if we have a timeout or no response.但是,如果我确实选择了后者,我只需让服务器每隔一个字节就“ping”每个客户端,看看我们是否有超时或没有响应。 You could even use a background thread to achieve this with precise timing.您甚至可以使用后台线程以精确的时间实现这一点。 Maybe even a combination could be implemented in some sort of options list (enum flags or something) if you're really worried about it.如果您真的很担心,甚至可以在某种选项列表(枚举标志或其他东西)中实现组合。 But it's no so big a deal to have a little delay in updating the server, as long as you DO update.但是,只要您确实更新,更新服务器的延迟就没什么大不了的。 It's the internet, and no one expects it to be magic!这是互联网,没有人期望它是魔术! :) :)

Implementing heartbeat into your system might be a solution.在您的系统中实施心跳可能是一个解决方案。 This is only possible if both client and server are under your control.这只有在客户端和服务器都在您的控制之下时才有可能。 You can have a DateTime object keeping track of the time when the last bytes were received from the socket.您可以让 DateTime 对象跟踪从套接字接收到最后一个字节的时间。 And assume that the socket not responded over a certain interval are lost.并假设在特定时间间隔内未响应的套接字丢失。 This will only work if you have heartbeat/custom keep alive implemented.这仅在您实现了心跳/自定义保持活动时才有效。

I've found quite useful, another workaround for that!我发现它非常有用,这是另一种解决方法!

If you use asynchronous methods for reading data from the network socket (I mean, use BeginReceive - EndReceive methods), whenever a connection is terminated;如果您使用异步方法从网络套接字读取数据(我的意思是,使用BeginReceive - EndReceive方法),则无论何时终止连接; one of these situations appear: Either a message is sent with no data (you can see it with Socket.Available - even though BeginReceive is triggered, its value will be zero) or Socket.Connected value becomes false in this call (don't try to use EndReceive then).出现以下情况之一:要么发送没有数据的消息(您可以使用Socket.Available看到它 - 即使触发了BeginReceive ,其值也将为零)或Socket.Connected值在此调用中变为 false(不要然后尝试使用EndReceive )。

I'm posting the function I used, I think you can see what I meant from it better:我发布了我使用的函数,我想你可以更好地理解我的意思:


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

This worked for me, the key is you need a separate thread to analyze the socket state with polling.这对我有用,关键是您需要一个单独的线程来通过轮询分析套接字状态。 doing it in the same thread as the socket fails detection.在与套接字失败检测相同的线程中执行此操作。

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

The example code here http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx shows how to determine whether the Socket is still connected without sending any data.此处的示例代码http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx显示了如何在不发送任何数据的情况下确定 Socket 是否仍然连接。

If you called Socket.BeginReceive() on the server program and then the client closed the connection "gracefully", your receive callback will be called and EndReceive() will return 0 bytes.如果您在服务器程序上调用 Socket.BeginReceive() 然后客户端“正常”关闭连接,您的接收回调将被调用并且 EndReceive() 将返回 0 字节。 These 0 bytes mean that the client "may" have disconnected.这些 0 字节意味着客户端“可能”已断开连接。 You can then use the technique shown in the MSDN example code to determine for sure whether the connection was closed.然后,您可以使用 MSDN 示例代码中显示的技术来确定连接是否已关闭。

Expanding on comments by mbargiel and mycelo on the accepted answer, the following can be used with a non-blocking socket on the server end to inform whether the client has shut down.扩展mbargielmycelo对已接受答案的评论,以下内容可以与服务器端的非阻塞套接字一起使用,以通知客户端是否已关闭。

This approach does not suffer the race condition that affects the Poll method in the accepted answer.这种方法不会受到影响已接受答案中的 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;
    }
}

The approach is based on the fact that the Socket.Receive method returns zero immediately after the remote end shuts down its socket and we've read all of the data from it.该方法基于这样一个事实,即Socket.Receive方法在远程端关闭其套接字后立即返回零,并且我们已经从中读取了所有数据。 From Socket.Receive documentation :Socket.Receive 文档

If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, the Receive method will complete immediately and return zero bytes.如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且所有可用数据都已接收到,则 Receive 方法将立即完成并返回零字节。

If you are in non-blocking mode, and there is no data available in the protocol stack buffer, the Receive method will complete immediately and throw a SocketException.如果您处于非阻塞模式,并且协议栈缓冲区中没有可用数据,则 Receive 方法将立即完成并抛出 SocketException。

The second point explains the need for the try-catch.第二点解释了 try-catch 的必要性。

Use of the SocketFlags.Peek flag leaves any received data untouched for a separate receive mechanism to read.使用SocketFlags.Peek标志使任何接收到的数据保持不变,以便单独的接收机制读取。

The above will work with a blocking socket as well, but be aware that the code will block on the Receive call (until data is received or the receive timeout elapses, again resulting in a SocketException ).以上也适用于阻塞套接字,但请注意,代码将在 Receive 调用上阻塞(直到接收到数据或接收超时,再次导致SocketException )。

Can't you just use Select?你不能只使用 Select 吗?

Use select on a connected socket.在连接的套接字上使用 select。 If the select returns with your socket as Ready but the subsequent Receive returns 0 bytes that means the client disconnected the connection.如果选择返回时您的套接字为就绪,但随后的接收返回 0 字节,这意味着客户端断开了连接。 AFAIK, that is the fastest way to determine if the client disconnected. AFAIK,这是确定客户端是否断开连接的最快方法。

I do not know C# so just ignore if my solution does not fit in C# (C# does provide select though) or if I had misunderstood the context.我不知道 C#,所以如果我的解决方案不适合 C#(尽管 C# 确实提供了选择)或者我是否误解了上下文,请忽略。

Using the method SetSocketOption, you will be able to set KeepAlive that will let you know whenever a Socket gets disconnected使用 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 http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx

Hope it helps!希望能帮助到你! Ramiro Rinaldi拉米罗·里纳尔迪

i had same problem , try this :我有同样的问题,试试这个:

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

This is in VB, but it seems to work well for me.这是在 VB 中,但它似乎对我来说效果很好。 It looks for a 0 byte return like the previous post.它像上一篇文章一样寻找 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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM