优雅地终止基于 Boost Asio 的 Windows 控制台应用程序

Posted

技术标签:

【中文标题】优雅地终止基于 Boost Asio 的 Windows 控制台应用程序【英文标题】:Gracefully terminate a Boost Asio based Windows console application 【发布时间】:2014-12-11 20:23:54 【问题描述】:

我正在开发基于 boost.asio 的 HTTP 服务器。它应该在外部停止。我们使用 asio 信号处理,它对 ctrl-c 效果很好,但不处理 WM_CLOSE,因此没有直接的方法可以从外部优雅地关闭应用程序,例如通过taskkill。强制终止进程不是一种选择。有没有一种已知的方法来解决这个问题?

【问题讨论】:

您能否尝试使用start "MyApp" myapp.exe 启动您的应用程序,然后使用taskkill /FI "WINDOWTITLE eq MyApp*" 向您的应用程序发送一个可以使用SetConsoleCtrlHandler 设置的处理程序处理的事件(请参阅下面的答案)?这样你就杀死了包含cmd.exe,然后将CTRL_CLOSE_EVENT(IIRC)发送到你的应用程序。 谢谢!这也非常有用。有没有一种简单的方法可以隐藏控制台(不使用第三方实用程序)?如果不是,我想我可以编写一个简单的启动器,为我的进程创建一个新的隐藏控制台。 我希望 MSVC 将 CTRL_CLOSE_EVENT 映射到某个信号,以便 asio 可以捕获它,但显然这不会发生。尝试过 SIGINT、SIGTERM、SIGBREAK、SIGABRT、SIGABRT_COMPAT。有什么想法吗? @DmitryShubin,MSVC 将 Ctrl+C 映射到 SIGINT,所有其他事件都映射到 SIGBREAK。我刚刚使用通过signal 安装的简单SIGBREAK 处理程序进行了测试。这绝对可以处理CTRL_CLOSE_EVENT。问题是 taskkill.exe 只会将WM_CLOSE 发送给控制台的有效所有者,由枚举窗口和调用GetWindowsThreadProcessId 确定。通常这是分配控制台的进程,如果它仍在运行。但是关闭控制台也会杀死所有附加到它的进程;他们有 5 秒的时间干净地退出。 【参考方案1】:

更新

只需使用您“通常”使用的任何 IPC 方法

    编写一个简单的过程控制实用程序,使用例如named_condition 通知您的 asio 进程关闭。

    请注意,named_codition 在某种程度上等同于 Win32 命名事件,以防您认为这个固有平台特定的代码更简单

    考虑创建一个Windows Service (NTService),因为看起来这就是你想要的

    作为一个长远的目标,尝试从您的进程中创建一个***窗口; AllocConsoleCreateWindow 都可以提供帮助。但是,您最终会得到更多特定于平台的东西


原答案,处理如何监听控制台关闭事件:

这真的与使用 Boost Asio 无关。当然,在 POSIX 平台上,您可以使用 boost::asio::signal_set 来处理 SIG_INT 和 SIG_TERM 信号。

但是,您使用的是 Windows,并且没有可移植的方法来检测控制台关闭事件。

您应该编写一个检测 CTRL_CLOSE_EVENT(和 CTRL_C_EVENT,如果需要)的控制台处理程序例程,并使用 SetConsoleCtrlHandler 将处理程序例程添加到您的进程中。

#include <windows.h> 
#include <stdio.h> 

BOOL CtrlHandler( DWORD fdwCtrlType ) 
 
  switch( fdwCtrlType ) 
   
    // Handle the CTRL-C signal. 
    case CTRL_C_EVENT: 
      printf( "Ctrl-C event\n\n" );
      Beep( 750, 300 ); 
      return( TRUE );

    // CTRL-CLOSE: confirm that the user wants to exit. 
    case CTRL_CLOSE_EVENT: 
      Beep( 600, 200 ); 
      printf( "Ctrl-Close event\n\n" );
      return( TRUE ); 

    // Pass other signals to the next handler. 
    case CTRL_BREAK_EVENT: 
      Beep( 900, 200 ); 
      printf( "Ctrl-Break event\n\n" );
      return FALSE; 

    case CTRL_LOGOFF_EVENT: 
      Beep( 1000, 200 ); 
      printf( "Ctrl-Logoff event\n\n" );
      return FALSE; 

    case CTRL_SHUTDOWN_EVENT: 
      Beep( 750, 500 ); 
      printf( "Ctrl-Shutdown event\n\n" );
      return FALSE; 

    default: 
      return FALSE; 
   
 

int main( void ) 
 
  if( SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) ) 
   
    printf( "\nThe Control Handler is installed.\n" ); 
    printf( "\n -- Now try pressing Ctrl+C or Ctrl+Break, or" ); 
    printf( "\n    try logging off or closing the console...\n" ); 
    printf( "\n(...waiting in a loop for events...)\n\n" ); 

    while( 1 )  
   
  else 
  
    printf( "\nERROR: Could not set control handler"); 
    return 1;
  
return 0;

要与 Boost Asio 协调,您可以让处理程序停止(全局)io_service 对象,并可能为运行操作设置一些标志。最后,您可能必须取消任何正在进行的异步操作(例如deadline_timers)。

一旦你这样做了,它应该很干净。

【讨论】:

不幸的是,这仅在我按 ctrl-c 或 ctrl-break 时有效,但如果我从外面杀死它则无效。具体来说,调用 taskkill 会给出:“错误:无法终止 PID 为 11656 的进程“term_test.exe”。 '原因:这个进程只能被强制终止(使用 /F 选项)。' 好的,谢谢您的建议。我同意 Windows 服务是正确的选择,但由于某些原因,我们的客户更喜欢控制台。可能会使用窗口解决方案。您能否更新您的初始回复,使其更符合问题,我可以接受? @DmitryShubin,通常没有可靠的方法来向单个控制台应用程序发出信号。但是,如果您知道它是作为进程组负责人启动的,那么您可以附加到它的控制台并将 Ctrl+Break(即 C SIGBREAK)发送到整个组。如果它不是组长,则最终将 Ctrl+Break 发送到连接到控制台的每个进程。此外,只有一个进程是控制台的有效所有者,在没有/F 的情况下通过WM_CLOSE 杀死。但是,这也会杀死所有附加的进程,因为 Ctrl+Close 事件允许 5 秒后干净地退出,然后终止。 注释“CTRL-CLOSE:确认用户想要退出”自 Vista 以来不再有效。控制台应用程序无法阻止控制台关闭。他们有 5 秒的时间优雅地退出。然后csrss.exe(代表conhost.exe注入控制线程)将通过TerminateProcess强行杀死它们,退出代码STATUS_CONTROL_C_EXIT。处理程序应该进行任何必要的清理,如果它干净地退出,则使用更好的状态代码调用exit

以上是关于优雅地终止基于 Boost Asio 的 Windows 控制台应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何优雅地关闭 boost asio ssl 客户端?

如何在到达终止字符时返回 boost::asio::async_read

boost::asio::ip::tcp::acceptor 在使用 async_accept 接收连接请求时终止应用程序

使用 Boost Asio 在 TCP 套接字上执行异步写入操作

安全地完成读取操作 (Boost.Asio)

Boost.Asio c++ 网络编程翻译