示范如何在命令行程序中处理用户中…

来源:互联网 发布:淘宝砍价话术 编辑:程序博客网 时间:2024/05/05 03:06
   实现捕获、处理用户中断信号的命令行程序还是比较繁琐的。尤其是Linux,Windows有一定差异。下面我实现了一个跨平台的捕获、处理用户中断信号(ctrl+c)的命令行程序框架。在windows下用SetConsoleCtrlHandlerAPI捕获处理中断,在Linux下用信号机制。
===================================================
   先看看interrupttable_console_program.h文件
===================================================
#pragma once

#include <boost/thread.hpp>

class InterruptServiceHandler
{
public:
    virtual~InterruptServiceHandler(){}
    virtual voidrun() = 0;
    virtual voidbeforeInterruptQuit() = 0;
    virtual voidbeforeNormalQuit() = 0;
};

class InterruptService
{
public:
   InterruptService(InterruptServiceHandler* handler_);
    voidjoin(boolgrace_quit_flag_);   
private:   
   InterruptServiceHandler* handler;
   boost::thread_group grp;
    boolgrace_quit_flag;
    boost::mutexsingle_close_mtx;
    boolsingle_close_flag;
    boolcheckFirstClose();
#ifdef WIN32
    voidsignalHandler();
#else
    voiddetectSignalThread();
#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
    : publicInterruptServiceHandler
{
public:
   TestService(int max_)
       :running_flag(true)
       ,max(max_)
       ,cnt(0)
    {
    }
    virtual voidrun()
    {
       while(running_flag)
       {
         boost::mutex::scoped_lock lock(mtx);
          if (cnt< max)
          {
             ++cnt;            
          }
          else
          {
             break;
          }
       }
    }
    virtual voidbeforeInterruptQuit()
    {
      boost::mutex::scoped_lock lock(mtx);
       running_flag= false;
       cout<< "InterruptQuit after lock "<< cnt<< " times"<< endl;
    }
    virtual voidbeforeNormalQuit()
    {
      boost::mutex::scoped_lock lock(mtx);
       cout<< "NormailQuit after lock "<< cnt<< " times"<< endl;
    }
public:
    boost::mutexmtx;
    intmax;
    intcnt;
    boolrunning_flag;
};

int main()
{
    TestServicetest_service(1000000);
   InterruptService service(&test_service);
   service.join(true);
    cout<< "Hello,World!"<<endl;//如果join(true),一定会看到这行字,如果join(false),用户中断的时候就看不到这行字了
    return0;

======================================================
   最后是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)
    {
    caseCTRL_C_EVENT:
    caseCTRL_BREAK_EVENT:
    caseCTRL_CLOSE_EVENT:
    caseCTRL_SHUTDOWN_EVENT:
      before_interrupt_quit_func();
       returnTRUE;
   default:
       returnFALSE;
    }
}
void InterruptService::signalHandler()
{
    if(checkFirstClose())
    {
      handler->beforeInterruptQuit();
       if(!grace_quit_flag)
       {
         exit(0);
              
              
}
#else
void InterruptService::detectSignalThread()
{
    // Wait forsignal indicating time to shut down.
    sigset_twait_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 intINTERRUPT_SERVICE_INSTANCE_TIMES = 0;
    if((INTERRUPT_SERVICE_INSTANCE_TIMES++) != 0)
    {
       throwstd::runtime_error("misuse: InterruptService should only beinstance 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 allsignals for background thread.
    sigset_tnew_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_twait_mask;
   sigemptyset(&wait_mask);
   sigaddset(&wait_mask, SIGINT);
   sigaddset(&wait_mask, SIGQUIT);
   sigaddset(&wait_mask, SIGTERM);
   pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
   boost::threaddetect_signal_thread(boost::bind(&InterruptService::detectSignalThread,this));
    sigset_tnew_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;
       returntrue;      
     
    else
    {
       returnfalse;
    }
}

0 0
原创粉丝点击