实现捕获、处理用户中断信号的命令行程序还是比较繁琐的。尤其是Linux,Windows有一定差异。下面我实现了一个跨平台的捕获、处理用户中断信号(ctrl+c)的命令行程序框架。在windows下用SetConsoleCtrlHandler
API捕获处理中断,在Linux下用信号机制。
===================================================
先看看interrupttable_console_program.h文件
===================================================
#pragma once
#include <boost/thread.hpp>
class InterruptServiceHandler
{
public:
virtual
~InterruptServiceHandler(){}
virtual void
run() = 0;
virtual void
beforeInterruptQuit() = 0;
virtual void
beforeNormalQuit() = 0;
};
class InterruptService
{
public:
InterruptService(InterruptServiceHandler* handler_);
void
join(bool
grace_quit_flag_);
private:
InterruptServiceHandler* handler;
boost::thread_group grp;
bool
grace_quit_flag;
boost::mutex
single_close_mtx;
bool
single_close_flag;
bool
checkFirstClose();
#ifdef WIN32
void
signalHandler();
#else
void
detectSignalThread();
#endif
};
======================================================
InterruptServiceHandler是纯虚接口类,可以看到需要实现三个函数:
run,你想要做的事情;
beforeInterruptQuit,发生中断时(用户输入ctrl+c)想要执行的代码;
beforeNormalQuit,run正常结束时想要执行的代码,执行它的时候run一定已经结束。
框架保证了beforeInterruptQuit和beforeNormalQuit只会有一个被调用。
解释:接口里最让人疑惑之处应该是为什么需要beforeNormalQuit,这块代码貌似放在run函数的最后面即可。这样设计是为了避免执行两次退出清理代码。因为有可能中断处理的时候run函数也结束了。也有可能run函数结束后正在清理的时候发生中断。
再解释:InterruptServiced的join的参数很重要。如果设置为true的话,程序会用优雅的方式退出,即必须在beforeInterruptQuit里通知run结束,等run结束后join函数返回,继续执行join之后的代码。这种方式的话如果beforeInterruptQuit没有合适地通知run结束,或者run无法结束(比如锁死在哪个疙瘩),则程序无法退出。如果join参数设置为false,则在调用beforeInterruptQuit之后直接调用exit(0)退出进程。后一种方式比较不优雅,但是其实很多时候都已经要退出进程了,做做清理工作也就ok了。不一定很care要run执行完毕。前者优雅,后者简单。根据实际情况各取所需吧。
======================================================
来个示例吧。main.cpp
======================================================
#include <iostream>
#include "interrupttable_console_program.h"
using namespace std;
using namespace global;
class TestService
: public
InterruptServiceHandler
{
public:
TestService(int max_)
:
running_flag(true)
,
max(max_)
,
cnt(0)
{
}
virtual void
run()
{
while
(running_flag)
{
boost::mutex::scoped_lock lock(mtx);
if (cnt
< max)
{
++
cnt;
}
else
{
break;
}
}
}
virtual void
beforeInterruptQuit()
{
boost::mutex::scoped_lock lock(mtx);
running_flag
= false;
cout
<< "InterruptQuit after lock "
<< cnt
<< " times"
<< endl;
}
virtual void
beforeNormalQuit()
{
boost::mutex::scoped_lock lock(mtx);
cout
<< "NormailQuit after lock "
<< cnt
<< " times"
<< endl;
}
public:
boost::mutex
mtx;
int
max;
int
cnt;
bool
running_flag;
};
int main()
{
TestService
test_service(1000000);
InterruptService service(&test_service);
service.join(true);
cout
<< "Hello,World!"
<<
endl;//如果join(true),一定会看到这行字,如果join(false),用户中断的时候就看不到这行字了
return
0;
}
======================================================
最后是interrupttable_console_program.cpp
======================================================
#include "interrupttable_console_program.h"
#include <boost/bind.hpp>
#include <stdexcept>
#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#endif
#ifdef WIN32
boost::function0<void>
before_interrupt_quit_func;
BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
{
switch
(ctrl_type)
{
case
CTRL_C_EVENT:
case
CTRL_BREAK_EVENT:
case
CTRL_CLOSE_EVENT:
case
CTRL_SHUTDOWN_EVENT:
before_interrupt_quit_func();
return
TRUE;
default:
return
FALSE;
}
}
void InterruptService::signalHandler()
{
if
(checkFirstClose())
{
handler->beforeInterruptQuit();
if
(!grace_quit_flag)
{
exit(0);
}
}
}
#else
void InterruptService::detectSignalThread()
{
// Wait for
signal indicating time to shut down.
sigset_t
wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGINT);
sigaddset(&wait_mask, SIGQUIT);
sigaddset(&wait_mask, SIGTERM);
int sig =
0;
sigwait(&wait_mask, &sig);
if
(checkFirstClose())
{
handler->beforeInterruptQuit();
if
(!grace_quit_flag)
{
exit(0);
}
}
}
#endif
InterruptService::InterruptService(InterruptServiceHandler*
handler_)
: handler(handler_)
, grace_quit_flag(true)
, single_close_flag(false)
{
static int
INTERRUPT_SERVICE_INSTANCE_TIMES = 0;
if
((INTERRUPT_SERVICE_INSTANCE_TIMES++) != 0)
{
throw
std::runtime_error("misuse: InterruptService should only be
instance once!");
}
#ifdef WIN32
grp.create_thread(boost::bind(&InterruptServiceHandler::run,
handler));
before_interrupt_quit_func =
boost::bind(&InterruptService::signalHandler,
this);
SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
#else
// Block all
signals for background thread.
sigset_t
new_mask;
sigfillset(&new_mask);
pthread_sigmask(SIG_BLOCK, &new_mask, NULL);
grp.create_thread(boost::bind(&InterruptServiceHandler::run,
handler));
#endif
}
void InterruptService::join(bool grace_quit_flag_)
{
grace_quit_flag = grace_quit_flag_;
#ifndef WIN32
sigset_t
wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGINT);
sigaddset(&wait_mask, SIGQUIT);
sigaddset(&wait_mask, SIGTERM);
pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
boost::thread
detect_signal_thread(boost::bind(&InterruptService::detectSignalThread,
this));
sigset_t
new_mask;
sigfillset(&new_mask);
pthread_sigmask(SIG_BLOCK, &new_mask, NULL);
#endif
grp.join_all();
if
(checkFirstClose())
{
handler->beforeNormalQuit();
}
}
bool InterruptService::checkFirstClose()
{
boost::mutex::scoped_lock lock(single_close_mtx);
if
(!single_close_flag)
{
single_close_flag = true;
return
true;
}
else
{
return
false;
}
}