检测意外的套接字断开连接

5

这不是一个关于如何做的问题,而是关于我所做的是否有错的问题。我已经阅读过,如果在等待数据(BeginReceive)时,没有使用定时器或定期发送消息等方式,就不能检测到套接字意外关闭(例如杀死服务器/客户端进程,拔掉网络电缆)。但是我已经使用了以下设置相当长的时间来实现此目的,至今一直完美地工作。

public void OnReceive(IAsyncResult result)
{
    try
    {
        var bytesReceived = this.Socket.EndReceive(result);

        if (bytesReceived <= 0)
        {
            // normal disconnect
            return;
        }

        // ...

        this.Socket.BeginReceive...;
    }
    catch // SocketException
    {
        // abnormal disconnect
    }
}

现在,既然我已经阅读了不容易实现的内容,我想知道我的方法是否有问题。是吗?还是结束进程和拔掉电缆等操作之间有区别?

1个回答

12

这是完全可行和可以的。一般思路如下:

如果 EndReceive 返回值不为零,则表示有待处理的传入数据。

如果 EndReceive 返回值为零,则表示远程主机已关闭其连接端点。这意味着如果它被编程为这样做,它仍然可以接收您发送的数据,但无论如何都不能再发送自己的数据了。通常在这种情况下,您也会关闭自己的连接端点,完成有序关闭,但这不是强制性的。

如果 EndReceive 抛出异常,则表示连接发生了异常终止(进程被杀死、网络电缆断开、电源丢失等)。

您需要注意以下几点:

  1. EndReceive 永远不可能返回小于零的值(您代码中的测试是具有误导性的)。
  2. 如果它抛出异常,除了 SocketException 之外,还可能抛出其他类型的异常。
  3. 如果它返回零,则必须小心停止调用 BeginReceive; 否则,您将开始无限而毫无意义的 BeginReceiveEndReceive 的乒乓游戏(它会显示在 CPU 使用率上)。您的代码已经做到了这一点,因此不需要更改任何内容。

+1 特别喜欢你的表述方式:“如果程序被编程为这样做”。 - Andrew Barber
2
+1 我还想指出的是,除非重新抛出异常,否则不应该有一个捕获所有异常的 catch 语句。 - Alex L
1
@Alex 我不同意禁止捕获所有异常,除非你重新抛出它们。我有一个 TCP 服务器类,它的工作方式非常像这里的一个,我不希望一些随机异常终止整个服务器应用程序。这就是为什么我捕获所有异常并将它们传递给 OnExceptionCaught 事件处理程序,该处理程序应连接到某些日志记录功能等。 - Algoman
1
未在接收回调中捕获的异常(就像这里一样),会通过线程池传递到线程 - 这是第一次机会异常的结束,因此Visual Studio会以“用户代码未处理异常”为消息停止应用程序。这仅在您在Visual Studio中运行程序时发生,并且您可以恢复运行,但是除非您有一个AppDomain.UnhandledException的事件处理程序,否则应用程序将被终止。在我看来,这个事件不是为了这个目的。AppDomain.UnhandledException更像是为了编写事后调试的堆栈跟踪的最后手段... - Algoman

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