如何检测C#中的断开连接套接字?

4
我一直在使用C#编写套接字客户端程序,并想知道如何检测套接字的另一端是否已经“不正常”断开,例如网络电缆被拔出或硬重置。我有以下函数来访问套接字,根据这里MSDN文章中的SO问题,检查断开的套接字的最佳方法是发送一个长度为0的1字节消息。如果抛出异常并且WSAEWOULDBLOCK不是错误代码,则套接字已断开连接。我尝试过这种方式,在服务器连接硬重置后,客户端将调用Send(new byte[1], 0, 0, SocketFlags.None)并成功返回,紧接着Receive()命令则返回WSAEWOULDBLOCK错误。
下面是我的代码。 _socket设置为非阻塞模式:
private int nonBlockRecv(byte[] recvBytes, int offset, int size, SocketFlags sf)
{
    int bytesRecv = 0;

    while (true)
    {
        try
        {
            nonBlockSend(new byte[1], 0, 0, sf);
            bytesRecv = _socket.Receive(recvBytes, offset, size, sf);
            break;
        }
        catch (SocketException excp)
        {
            if (excp.ErrorCode != 10035) // WSAEWOULDBLOCK
                throw excp;
        }
    }

    return bytesRecv;
}

private int nonBlockSend(byte[] sendBytes, int offset, int size, SocketFlags sf)
{
    int bytesSent = 0;

    while (true)
    {
        try
        {
            _socket.Send(sendBytes, offset, size, sf);
            break;
        }
        catch (SocketException excp)
        {
            if (excp.ErrorCode != 10035) // WSAEWOULDBLOCK
                throw excp;
        }
    }

    return bytesSent;
}

编辑:这可能是有益的,但服务器是Windows Mobile设备。我在另一个帖子中看到,不同的操作系统可能能够发送套接字关闭信号,当它们崩溃时。也许Windows Mobile操作系统不能做到这一点?


一个1字节的消息怎么可能长度为零呢? :-) - Ondrej Tucny
很好的问题哈哈。这就是MSDN文章所说要做的。但是,如果我将大小设置为1,那么它就可以工作了!我想这很明显,但是那个额外的位被发送到流中并传递到服务器端套接字。如果套接字关闭,我希望Receive()抛出一个除了阻塞之外的异常,但似乎情况并非如此... - Zac
2个回答

6

如果远程计算机正常断开会话,Socket.Receive()方法将返回0字节。您必须检测到这一点才能知道远程端已经断开连接:

int recv = sock.Receive(data);
if (recv == 0)
{
    // Remote client has disconnected.
}
else
{
    // Remote client has sent data.
}

此外,即使出现 SocketException,您也可以识别套接字断开的异常。

希望这能帮助解决您的问题。


但是如果会话以非正常的方式关闭,例如网络电缆被拔掉怎么办?Receive()调用不会抛出异常或返回,它只会继续阻塞。 - Zac
我已经编辑了我的帖子,但服务器端是一个WM6设备。当服务器关闭程序时,我会在客户端程序上收到通知,表示套接字连接已关闭,但在我硬重置设备时却没有收到通知。 - Zac
看起来在 WM6 套接字的接收端没有直接的方法可以判断连接是否被取消。不过您的方法在大多数其他情况下都有效,非常感谢! - Zac
1
在这种情况下,graceful是什么意思?即远程计算机调用serverSocket.disconnect() - user2635088

1

我知道现在有点晚了,但我想出了一个巧妙的解决方案。

我需要与第三方软件通信,该软件希望在每个发送的命令中都包含回车符,否则它将忽略该命令。

在主要阶段,我的客户端套接字处于循环状态,接收来自第三方软件的响应。我的解决方案并不理想,但基本原则是对套接字设置接收超时,以便循环尝试读取5秒钟,然后进入catch块,再次循环。在每次接收之前,我调用自己的isconnected方法,该方法执行一个小写操作而没有回车符,因此第三方软件会忽略它,但如果网络断开,它将为我提供可靠的故障转移。如果写操作失败,我只需抛出LostConnectionException并在外部处理即可。

如果您正在编写服务器和客户端,则可以轻松地想出一些检查位数,其他人会忽略。

这可能不是完美的,但对我来说是可靠的。

while (numberOfBytesRead == 0)
{          
    try
    {
        IsConnected();
        _socket.ReceiveTimeout = 5000;
        numberOfBytesRead = _socket.Receive(myReadBuffer, 0, myReadBuffer.Length, SocketFlags.None);

    }
    catch (Exception e)
    {
        if (e.GetType() == typeof (LostConnection))
        {
            Status = SocketStatus.offline;
            throw;
        }
    }
}

而isconnected方法看起来会像这样

public bool IsConnected(Socket s)
{
    try
    {
        ASCIIEncoding encoder = new ASCIIEncoding();
        byte[] buffer = encoder.GetBytes("test");
        s.Send(buffer, 0, buffer.Length, SocketFlags.None);
    }
    catch (Exception)
    {
        throw new LostConnection();
    }
    return s.Connected;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接