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

.net 立即检测客户端与服务器套接字的断开连接,.net,c#,sockets,tcp,connection,.net,C#,Sockets,Tcp,Connection,如何检测客户端已断开与服务器的连接 在我的AcceptCallBack方法中有以下代码 static Socket handler = null; public static void AcceptCallback(IAsyncResult ar) { //Accept incoming connection Socket listener = (Socket)ar.AsyncState; handler = listener.EndAccept(ar); } 我需要找到一种方法,

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

在我的
AcceptCallBack
方法中有以下代码

static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
  //Accept incoming connection
  Socket listener = (Socket)ar.AsyncState;
  handler = listener.EndAccept(ar);
}
我需要找到一种方法,尽快发现客户端已从
处理程序
套接字断开连接

我试过:

  • handler.Available
  • handler.Send(新字节[1],0,
    SocketFlags(无)
  • handler.Receive(新字节[1],0,
    SocketFlags(无)
  • 当您连接到服务器并希望检测服务器何时断开连接时,上述方法有效,但当您是服务器并希望检测客户端断开连接时,上述方法无效


    任何帮助都将不胜感激。

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

    当连接正常关闭时,会通知另一方。但是,如果连接以其他方式断开(比如用户连接断开),那么服务器直到超时(或者尝试写入连接并且确认超时)才会知道。这就是TCP的工作方式,你必须接受它

    因此,“立即”是不现实的。您所能做的最好是在超时时间内,这取决于代码运行的平台

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

    因为在断开套接字连接时,没有可用的事件发出信号,所以您必须以您可以接受的频率轮询它

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

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

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

    在系统中实现heartbeat可能是一种解决方案。只有当客户端和服务器都在您的控制之下时,这才是可能的。可以让DateTime对象跟踪从套接字接收最后字节的时间。并假设在一定时间间隔内未响应的套接字丢失。只有在实现了heartbeat/custom-keep-alive的情况下,这才有效。

    您不能只使用Select吗

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

    static class SocketExtensions
    {
      public static bool IsConnected(this Socket socket)
      {
        try
        {
          return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
        }
        catch (SocketException) { return false; }
      }
    }
    
    我不知道C,所以如果我的解决方案不适合C,或者我误解了上下文,请忽略它。

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

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


    您可以在这里使用建议的方法,或者按照建议实现“heartbeat”。我选择前者。但是如果我选择了后者,我只需要让服务器每隔一段时间用一个字节“ping”每个客户机,看看我们是否有超时或没有响应。您甚至可以使用背景线程来实现这一点,并进行精确计时。如果您真的很担心,甚至可以在某种选项列表(枚举标志或其他)中实现组合。但是,只要您进行更新,在更新服务器时稍微延迟一点也没什么大不了的。这是互联网,没有人指望它会有魔力

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

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

    我正在发布我使用的功能,我想你可以从中更好地了解我的意思:


    这里的示例代码 演示如何在不发送任何数据的情况下确定套接字是否仍处于连接状态


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

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

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

    希望有帮助!
    Ramiro Rinaldi

    这对我来说很有效,关键是你需要一个单独的线程来分析带有轮询的套接字状态。在与套接字相同的线程中执行此操作会导致检测失败

    //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);
        }
    }
    
    //打开或接收服务器套接字-在此处执行您的代码
    插座=新插座(..);
    //启用“保持活动”,以便我们可以检测关闭
    socket.SetSocketOption(SocketOptionLevel.socket,SocketOptionName.KeepAlive,true);
    //创建一个线程,每5秒检查一次套接字是否仍然连接。TODO添加线程起始代码
    void MonitorSocketsForClosureWorker(){
    DateTime nextCheckTime=DateTime.Now.AddSeconds(5);
    而(!exitSystem){
    if(nextCheckTime    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);
    
    public void OnDataReceived(IAsyncResult asyn)
    {
        try
        {
            SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
    
            int iRx = socket.EndReceive(asyn);
        }
        catch (SocketException ex)
        {
            SocketExceptionCaught(ex);
        }
    }
    
    // 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;
        }
    }
    
    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
            }
        }
    }
    
    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