【问题标题】:how to handle code that never executes如何处理从不执行的代码
【发布时间】:2016-06-03 11:57:47
【问题描述】:

我有一些看起来像这样的代码,我不确定如何处理永远不会执行的部分,因为该代码的一部分在等待连接时无限循环运行,当我终止程序时,它会从只有那里。

main(){

// do some stuff....

    while(1) {
        int newFD =
            accept(sockFD, (struct sockaddr *)&client_addr, &client_addr_size);
        if(newFD == -1) {
            std::cerr << "Error while Accepting on socket" << std::endl;
            continue;
        }

        if(!fork()) {

            close(sockFD); // close child's sockfd - not needed here

            // lalala do stuff send message here                

            close(newFD);  // finally close its newFD - message sent, no use 
            return 0;
        }
        close(newFD);  // close parent's newFD - no use here
    }

    // now execution never reaches here
    close(sockFD);      // so how to handle this?
    freeaddrinfo(res);  // and this?

    return 0;
}

【问题讨论】:

  • 我让它保持原样,我猜操作系统应该处理它?
  • 对于这种情况,我想挂钩终止信号并使用它干净地退出程序。不过,一般来说,对于从未执行过的代码,请将其删除。让您的版本控制跟踪旧的死代码。
  • 您的问题是关于如何处理关闭套接字,或者如何处理代码本身?
  • 也许这有点简单,但你能检查一个非常高的数字然后关闭吗?
  • 关闭newFD后为什么不把return 0改成continue

标签: c++ linux loops networking infinite-loop


【解决方案1】:

如果您的代码要被其他人使用或者您自己只是希望它更干净,您可以并且可能应该添加退出处理程序。在您的退出处理程序中,您可以切换一个使while() 循环终止的标志。以下代码可以 100% 正常工作,并且可靠且跨平台,但如果您想做更复杂的事情,您应该使用适当的线程安全操作系统特定函数或类似 Boost 或 C++11 的东西

首先声明两个全局变量,将它们设为 volatile,这样编译器将始终强制我们读取或写入其实际内存值。如果我们不将其声明为 volatile,那么编译器可能会将其值放入寄存器中,这将使其不起作用。使用 volatile 设置,它会在每个循环中读取内存位置并正常工作,即使使用多个线程也是如此。

volatile bool bRunning=true;
volatile bool bFinished=false;

而不是您的 while(1) {} 循环,将其更改为此

while(bRunning)
{
    dostuff
}
bFinished=true;

在您的退出处理程序中只需设置bRunning=false;

void ExitHandler()
{
    bRunning=false;
    while(bFinished==false) { Sleep(1); }
}

您没有指定操作系统,但看起来您是基于 Linux 的,要在 Linux 上设置处理程序,您需要这个。

void ExitHandler(int s)
{
    bRunning=false;
}

int main()
{
    struct sigaction sigIntHandler;
    sigIntHandler.sa_handler = ExitHandler;
    sigemptyset(&sigIntHandler.sa_mask);
    sigIntHandler.sa_flags = 0;
    sigaction(SIGINT, &sigIntHandler, NULL);
    while(bRunning)
    {
        dostuff
    }
    ...error_handling...
}

在 Windows 上,当您是控制台应用程序时,它如下所示。

BOOL WINAPI ConsoleHandler(DWORD CEvent)
{
    switch (CEvent)
    {
        case CTRL_C_EVENT:
        case CTRL_BREAK_EVENT:
        case CTRL_CLOSE_EVENT:
        case CTRL_LOGOFF_EVENT:
        case CTRL_SHUTDOWN_EVENT:
            bRunning = false;
            while (bFinished == false) Sleep(1);
            break;
    }
    return TRUE;
}

int main()
{
    SetConsoleCtrlHandler(ConsoleHandler, TRUE);
    while(bRunning()
    {
        dostuff
    }
    ...error_handling...
}

请注意这里需要测试并等待bFinished。如果您不在 Windows 上执行此操作,您的应用程序可能没有足够的时间关闭,因为退出处理程序由单独的操作系统特定线程调用。在 Linux 上,这不是必需的,您需要退出处理程序才能继续主线程。

另外需要注意的是,默认情况下,Windows 在终止您之前只给您大约 5 秒的时间来关闭它。这在许多情况下是不幸的,如果需要更多时间,您将需要更改注册表设置(坏主意)或实现一个具有更好挂钩的服务。对于您的简单情况,它会很好。

【讨论】:

  • fork() 在 MS Windows 中?我不这么认为。 ;)
  • 呵呵。我刚刚添加了 Windows 代码,因为它与可能搜索相同答案但在 Windows 上的其他人相关。希望有用!
  • 也不要(PHANDLER_ROUTINE)ConsoleHandler,那只是自找麻烦。你不应该需要演员表。此外,使用 volatile 并希望获得最好的结果也不好。而是使用Interlocked* 系列函数。最后,不是在现有线程之一的上下文中执行 control-C 处理程序吗?如果您等待该线程终止,那将是致命的......
  • 你对演员阵容的看法是对的!感谢您的发现。在 Windows 上,它是一个单独的线程,如果您从 Handler 返回,您的应用程序将终止。 msdn.microsoft.com/en-us/library/windows/desktop/… "当接收到 CTRL_CLOSE_EVENT 信号时,控制处理程序返回 TRUE 并且进程终止。"我已经对此进行了测试,至少在 Windows Vista 及更高版本中是这样的。
  • @UlrichEckhardt 关于 volatile bool 它非常适合这个用例,100% 无故障。但如果你愿意,你可以用其他方式来做。
【解决方案2】:

对于这些事情,操作系统会在关机时妥善释放资源。但是,更一般地,您仍然需要确保分配的资源在程序执行期间不会堆积,即使它们被操作系统自动回收,因为这样的资源泄漏仍然会影响程序的行为和性能。

现在,关于手头的资源,没有理由不将它们视为 C++ 中的所有资源。公认的规则是将它们绑定到将在析构函数中释放它们的对象,另请参见 RAII 习语。这样,即使在稍后的某个阶段有人添加了 break 语句,代码仍然会正常运行。

顺便说一句:我在这里看到的更严重的问题是缺乏适当的错误处理。

【讨论】:

  • 您能评论一下“错误处理”部分吗?我很乐意根据您的建议改进我的代码,因为我以前从未做过这种工作。
  • 简单示例:fork()。如果您查看联机帮助页,它会返回三组不同的值,但您只能区分其中的两组。如果fork() 失败,我将throw std::runtime_error("fork() failed") 作为默认错误处理概念,因此不会被忽视。这也适用于此处的其他功能,只需阅读相应的文档以了解它们如何发出错误信号。您还可以在 codereview.stackexchange.com 上发布您的代码以供审核,以获得进一步的建议。
  • @UlrichEckhardt 提出了很好的观点,我建议尽可能使用 RAII 进行错误处理。您可能希望研究 ASIO 的网络而不是原始套接字功能,因为它将进入 C++17 标准,并允许您的应用程序跨平台,而无需您付出更多努力。 Windows 套接字和 Linux 套接字有一些差异,这使得移植比需要的麻烦更多!
  • @RylerSturden 感谢您的建议。如果我的 uni 课程不会强制要求采用这种低级方式,我肯定会使用它。
  • 无论如何,了解低级方法是件好事,这样您才能真正欣赏好的网络库。 ;) 您可以自己将该代码包装到类中,即 CSocket,并在析构函数中优雅地处理关闭套接字以及您需要做的任何其他事情。这样你自己的代码就是 RAII。我认为所有网络 C++ 程序员都曾编写过 CSocket 类型的类。 ;)
猜你喜欢
  • 1970-01-01
  • 2017-03-07
  • 2011-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-13
  • 1970-01-01
  • 2017-05-16
相关资源
最近更新 更多