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

时间:2009-04-06 16:38:40

标签: .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);
}

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

我试过了:

  1. handler.Available;
  2. handler.Send(new byte[1], 0, SocketFlags.None);
  3. handler.Receive(new byte[1], 0, SocketFlags.None);
  4. 当您连接到服务器并想要检测服务器何时断开连接但是当您是服务器时它们不能正常工作并想要检测客户端断开连接时,上述方法是有效的。

    任何帮助将不胜感激。

14 个答案:

答案 0 :(得分:96)

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

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

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 :(得分:12)

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

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

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

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

答案 2 :(得分:11)

有人提到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客户端和服务器之间的断开连接。

答案 3 :(得分:6)

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

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

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

答案 4 :(得分:2)

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

答案 5 :(得分: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.
}

答案 6 :(得分: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);
    }
}

答案 7 :(得分:0)

你不能只使用Select吗?

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

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

答案 8 :(得分:0)

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

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

答案 9 :(得分: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

希望它有所帮助! 拉米罗里纳尔迪

答案 10 :(得分:0)

在接受的答案上用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标志会使所有接收到的数据保持不变,以供单独的接收机制读取。

上面的方法也可以使用 blocking 套接字,但是要注意,代码将在Receive调用中阻塞(直到接收到数据或接收超时结束,再次导致{{ 1}})。

答案 11 :(得分: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
        }
    }
}

答案 12 :(得分:0)

这在VB中,但对我来说似乎很好。像上一篇文章一样,它寻找一个0字节的返回。

Copy

答案 13 :(得分:-1)

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