首页 文章

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

提问于
浏览
66

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

我的 AcceptCallBack 方法中有以下代码

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

我需要找到一种方法来尽快发现客户端与 handler 套接字断开连接 .

我试过了:

  • handler.Available;

  • handler.Send(new byte[1], 0, SocketFlags.None);

  • handler.Receive(new byte[1], 0, SocketFlags.None);

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

任何帮助将不胜感激 .

13 回答

  • 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; }
      }
    }
    
  • 2

    这根本不可能 . 您和服务器之间没有物理连接(极少数情况下,您使用环回电缆连接两个编译器) .

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

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

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

  • 0

    有人提到TCP套接字的keepAlive功能 . 这里描述得很好:

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

    我'm using it this way: after the socket is connected, I' m调用此函数,它将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客户端和服务器之间的断开连接 .

  • 2

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

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

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

  • 6

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

  • 11

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

    如果您使用异步方法从网络套接字读取数据(我的意思是,使用 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.
    }
    
  • 95

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

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

    你不能只使用Select吗?

    在连接的套接字上使用select . 如果select返回,套接字为Ready,则后续Receive返回0字节,表示客户端断开连接 . AFAIK,这是确定客户端是否断开连接的最快方法 .

    我不知道C#所以只要忽略我的解决方案是否适合C#(C#确实提供了select)或者我是否误解了上下文 .

  • 0

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

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

  • 12

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

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

  • 2

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

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

    如果远程主机使用Shutdown方法关闭Socket连接,并且已收到所有可用数据,则Receive方法将立即完成并返回零字节 . 如果您处于非阻塞模式,并且协议堆栈缓冲区中没有可用数据,则Receive方法将立即完成并抛出SocketException .

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

    使用 SocketFlags.Peek 标志会使任何接收的数据保持不变,以便单独的接收机制进行读取 .

    以上也适用于阻塞套接字,但要注意代码将在接收调用上阻塞(直到接收到数据或接收超时,再次导致 SocketException ) .

  • 0

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

相关问题