基于ACE的后台服务程序的实现
后台服务进程不属于任何一个终端会话,当然也就不用和任何用户交互,许多系统服务由后台服务进程实施;如网络服务,打印等。Windows和LINUX在实现后台服务进程上并不统一,Windows定义的名称为SERVICE而Linux上的名称为Daemon。对应到ACE上,对于Linux平台ACE提供了一个名称为ACE::daemonize()的静态方法,进程通过调用该方法即可成为后台服务进程;而对于Windows平台,ACE提供的是一个名称为 ACE_NT_Service的类,通过调用该类的一系列方法也可成为一个后台服务进程。下面分别讨论ACE::daemonize()及 ACE_NT_Service在ACE中的实现。
一、Linux下的后台服务程序
在Linux操作系统下实现后台服务程序用到ACE::daemonize()函数,该函数的实现在ACE.cpp文件中,对于Linux平台下生成daemon进程的完整步骤共7个,ACE在实现时有一步没有做。
/**
* Become a daemon process using thealgorithm in Richard Stevens
* "Advanced Programming in theUNIX Environment." If
* @a close_all_handles is non-zerothen all open file handles are
* closed.
*/
externACE_Export int daemonize (
const ACE_TCHAR pathname[] =ACE_TEXT ("/"),
bool close_all_handles =ACE_DEFAULT_CLOSE_ALL_HANDLES,
const ACE_TCHARprogram_name[] = ACE_TEXT ("<unknown>"));
该函数的具体实现如下:
int ACE::daemonize (const ACE_TCHAR pathname[],bool close_all_handles,const ACE_TCHAR program_name[]) { ACE_TRACE ("ACE::daemonize"); #if !defined (ACE_LACKS_FORK) pid_t pid = ACE_OS::fork ();
if (pid == -1) return -1; else if (pid != 0) ACE_OS::exit (0); // Parent exits.
// 1st child continues. ACE_OS::setsid (); // Become session leader.
ACE_OS::signal (SIGHUP, SIG_IGN);
pid = ACE_OS::fork (program_name);
if (pid != 0) ACE_OS::exit (0); // First child terminates.
// Second child continues.
if (pathname != 0) // change working directory. ACE_OS::chdir (pathname);
ACE_OS::umask (0); // clear our file mode creation mask.
// Close down the I/O handles. if (close_all_handles) { for (int i = ACE::max_handles () - 1; i >= 0; i--) ACE_OS::close (i);
int fd = ACE_OS::open ("/dev/null", O_RDWR, 0); if (fd != -1) { ACE_OS::dup2 (fd, ACE_STDIN); ACE_OS::dup2 (fd, ACE_STDOUT); ACE_OS::dup2 (fd, ACE_STDERR);
if (fd > ACE_STDERR) ACE_OS::close (fd); } }
return 0; #else ACE_UNUSED_ARG (pathname); ACE_UNUSED_ARG (close_all_handles); ACE_UNUSED_ARG (program_name);
ACE_NOTSUP_RETURN (-1); #endif /* ACE_LACKS_FORK */ } |
下面结合这些步骤以及ACE的实现源码分别说明:
1、fork出新的服务进程以便父进程退出,这样可以确保子进程不是进程组的组长
pid_t pid = ACE_OS::fork ();
if (pid == -1)
return -1;
else if (pid != 0)
ACE_OS::exit (0);
2、使用SETSID创建会话并设置子进程为进程组组长,该子进程不存在关联的控制终端
ACE_OS::setsid ();
ACE_OS::signal (SIGHUP, SIG_IGN);
3、再次Fork新的服务进程,该进程不是进程组的组长并且永远不能再次获取控制终端
pid = ACE_OS::fork (program_name);
if (pid != 0)
ACE_OS::exit (0);
4、改变当前文件系统目录,因为不做这个会导致系统管理员不能卸装(umount)一个文件系统
if (pathname != 0)
ACE_OS::chdir (pathname);
5、重新设置文件的访问属性,以便我们拥有对于我们写的任何东西的完全控制
ACE_OS::umask (0);
6、关闭所有的文件句柄
if (close_all_handles)
for (int i = ACE::max_handles () - 1; i >= 0; i--)
ACE_OS::close (i);
return 0;
另外,出于安全以及健壮性考虑,即使当前进程不使用stdin、stdout、stderr,也应重新打开0、1、2三个句柄,使之对应/dev/null。但是不知道什么原因ACE的实现代码中并没有该步骤。
二、Windows下的后台服务程序
1、Windows NT 服务程序的四个组成部分
1)、控制台应用程序的main函数
包括服务信息表的定义、调用系统函数 StartServiceCtrlDispatcher 连接程序主线程到服务控制管理程序。
2)、服务入口函数ServiceMain
执行服务初始化任务、服务的循环等。
3)、控制服务处理程序函数ServiceCtrlHandler
SCM(服务管理器)利用该函数和服务通信并控制程序的起停等。
4)、在系统运行此服务之前,需要先安装InstallService;服务不再需要时还涉及到服务的卸载UnstallService。
/***说明 服务程序的模板:一个服务程序必须有两个函数,一个是服务程序的主 函数ServiceMain(),另一个是服务程序的派遣函数ServiceHandler(), 它负责处理外部控制消息。 */ #include "stdafx.h"
SERVICE_STATUS ServiceStatus; SERVICE_STATUS_HANDLE hStatus;
int main(int argc, _TCHAR* argv[]) { //服务信息表 SERVICE_TABLE_ENTRY ServiceTable[2]; ServiceTable[0].lpServiceName=SRV_NAME; ServiceTable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain; ServiceTable[1].lpServiceName=NULL; ServiceTable[1].lpServiceProc=NULL;
StartServiceCtrlDispatcher(ServiceTable); return 0; }
void WINAPI ServiceMain( DWORD dwArgc, LPTSTR *lpszArgv) { DWORD errorCode = 0; DWORD specificError = 0xfffffff;
ServiceStatus.dwServiceType = SERVICE_WIN32; ServiceStatus.dwCurrentState = SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; hStatus=RegisterServiceCtrlHandler(SRV_NAME,(LPHANDLER_FUNCTION)ServiceCtrlHandler); if (hStatus==0) return;
// Handle error condition errorCode = GetLastError(); if (errorCode!=NO_ERROR) { ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; ServiceStatus.dwWin32ExitCode = errorCode; ServiceStatus.dwServiceSpecificExitCode = specificError; SetServiceStatus(hStatus, &ServiceStatus); return; }
// 初始化结束,报告运行状态 ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; SetServiceStatus(hStatus, &ServiceStatus);
//在这里做其他的工作 DWORD ret=MainWork(dwArgc,lpszArgv); //MainWork()函数为服务执行的函数,为一个循环。 if(0!=ret) {//MainWork异常退出 ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwServiceSpecificExitCode=S_FALSE; SetServiceStatus(hStatus,&ServiceStatus); return; }
ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode=S_OK; SetServiceStatus(hStatus, &ServiceStatus);
} void WINAPI ServiceCtrlHandler( DWORD fdwControl) { switch(fdwControl) { case SERVICE_CONTROL_PAUSE: ServiceStatus.dwCurrentState = SERVICE_PAUSED; break; case SERVICE_CONTROL_CONTINUE: ServiceStatus.dwCurrentState = SERVICE_RUNNING; break; case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; SetServiceStatus(hStatus,&ServiceStatus); return ;
case SERVICE_CONTROL_INTERROGATE: break;
default: break; }
SetServiceStatus(hStatus,&ServiceStatus); return ; } |
服务的安装和卸载如下:
//安装服务 BOOL InstallService() { SC_HANDLE hScMgr=NULL; SC_HANDLE hService=NULL;
hScMgr=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE); if(hScMgr==NULL) { printf("OpenSCManager() Error\n"); return FALSE; }
hService=CreateService(hScMgr,SRV_NAME, SRV_INFO,SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, SRV_PATH,0,0,0,0,0); if(hService!=NULL) printf("Create a new service successful!\n"); else { printf("Failed to create a new service! Error:OpenService() \n"); CloseServiceHandle(hScMgr);
return FALSE; }
CloseServiceHandle(hScMgr); CloseServiceHandle(hService); return TRUE; }
//卸载服务 BOOL UnstallService() { SC_HANDLE hScMgr,hService; SERVICE_STATUS status;
hScMgr=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE); if(NULL==hScMgr) { printf("OpenSCManager() Error\n"); return FALSE; }
hService=OpenService(hScMgr,SRV_NAME,SERVICE_ALL_ACCESS|DELETE); if(NULL==hService) { printf("OpenService() Error\n"); return FALSE; }
//查询服务状态 QueryServiceStatus(hService,&status);
//如果服务未停止,先停止它 if(status.dwCurrentState!=SERVICE_STOPPED) { ControlService(hService,SERVICE_CONTROL_STOP,&status); Sleep(500); }
BOOL bSuccess=DeleteService(hService); if(bSuccess) printf("Delete service successful!\n"); else { printf("Failed to delete service!\n"); return FALSE; }
return TRUE; }
|
2、基于ACE的Windows NT 服务程序
ACE_NT_Service类封装了Windows下的服务程序的实现框架,让开发者完全可以采用面向对象的思想来实现Windows服务程序。在ACE的源码包目录下(ACE_wrappers\examples\NT_Service)有一个示例程序,很好的讲述了一个服务程序的示例。基于ACE的Windows服务程序的实现包括如下几步:
1)、编写一个服务运行的类,该类继承自ACE_NT_Service,例如这里为MainNTService;
#ifndef _MAIN_NT_SVC_H_ #define _MAIN_NT_SVC_H_ #include "ace/NT_Service.h" #include "ace/Singleton.h" #include "ace/Mutex.h"
class MainNTService : public ACE_NT_Service { public: MainNTService (void):m_running(false){} ~MainNTService (void){}
virtual void handle_control (DWORD control_code); virtual int svc (void); private: bool m_running; }; typedef ACE_Singleton<MainNTService, ACE_Mutex> SERVICE; #endif /* #ifndef _MAIN_NT_SVC_H_ */ |
2)、实现父类的如下两个虚函数;
virtual voidhandle_control (DWORD control_code);
virtual int svc(void); //服务运行的主体函数
#include "main_service.h" #include "ace/OS_NS_unistd.h"
void MainNTService::handle_control (DWORD control_code) { if (control_code == SERVICE_CONTROL_SHUTDOWN || control_code == SERVICE_CONTROL_STOP) { report_status (SERVICE_STOP_PENDING); m_running=false; //告之svc()中的while循环结束 } ACE_NT_Service::handle_control (control_code); }
int MainNTService::svc (void) { m_running=true; ACE_DEBUG ((LM_DEBUG,ACE_TEXT ("Service::svc\n"))); report_status (SERVICE_RUNNING); while(m_running) //服务的核心循环体 { ACE_OS::sleep(1); }
ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Shutting down\n"))); return 0; } |
3)、为了便于服务的安装、卸载、启动、停止、设置运行模式等,编写参数选项类,例如为WinNTProcess;
#include "ace/Get_Opt.h" #include "ace/Init_ACE.h" #include "MainNTService.h"
// Default for the -i (install) option #define DEFAULT_SERVICE_INIT_STARTUP SERVICE_AUTO_START
class WinNTProcess { public: WinNTProcess (void); ~WinNTProcess (void);
int run(int argc, ACE_TCHAR* argv[]);
private: void parse_args (int argc,ACE_TCHAR* argv[]); void print_usage_and_die (void);
private: char progname[128];
int opt_install; int opt_remove; int opt_start; int opt_kill; int opt_type; int opt_debug;
int opt_startup; };
typedef ACE_Singleton<WinNTProcess, ACE_Mutex> PROCESS;
WinNTProcess::WinNTProcess (void) : opt_install (0), opt_remove (0), opt_start (0), opt_kill (0), opt_type (0), opt_debug (0), opt_startup (0) { ACE_OS::strcpy (progname, "service"); ACE::init (); }
WinNTProcess::~WinNTProcess (void) { ACE::fini (); }
void WinNTProcess::print_usage_and_die (void) { ACE_DEBUG ((LM_INFO, "Usage: %s" " -in -r -s -k -tn -d\n" " -i: Install this program as an NT service, with specified startup\n" " -r: Remove this program from the Service Manager\n" " -s: Start the service\n" " -k: Kill the service\n" " -t: Set startup for an existing service\n" " -d: Debug; run as a regular application\n", progname, 0)); ACE_OS::exit(1); }
void WinNTProcess::parse_args (int argc, ACE_TCHAR* argv[]) { ACE_Get_Opt get_opt (argc, argv, ACE_TEXT ("i:rskt:d")); int c;
while ((c = get_opt ()) != -1) switch (c) { case 'i': //安装 opt_install = 1; opt_startup = ACE_OS::atoi (get_opt.opt_arg ()); if (opt_startup <= 0) print_usage_and_die (); break; case 'r': //卸载 opt_remove = 1; break; case 's'://启动 opt_start = 1; break; case 'k'://停止 opt_kill = 1; break; case 't'://设置启动类型:自动、手动等 opt_type = 1; opt_startup = ACE_OS::atoi (get_opt.opt_arg ()); if (opt_startup <= 0) print_usage_and_die (); break; case 'd'://调试运行 opt_debug = 1; break; default: // -i can also be given without a value - if so, it defaults // to defined value. if (ACE_OS::strcmp (get_opt.argv ()[get_opt.opt_ind () - 1], ACE_TEXT ("-i")) == 0) { opt_install = 1; opt_startup = DEFAULT_SERVICE_INIT_STARTUP; } else { print_usage_and_die (); } break; } }
// Define a function to handle Ctrl+C to cleanly shut this down. static BOOL WINAPI ConsoleHandler (DWORD ctrlType) { //服务管理器控制入口函数 SERVICE::instance ()->handle_control (ctrlType); return TRUE; }
ACE_NT_SERVICE_DEFINE (Beeper,MainNTService,ACE_TEXT ("Annoying Beeper Service"));
int WinNTProcess::run (int argc, ACE_TCHAR* argv[]) { SERVICE::instance ()->name (ACE_TEXT ("Beeper"),ACE_TEXT ("Annoying Beeper Service"));
parse_args (argc, argv);
if (opt_install && !opt_remove) { if (-1 == SERVICE::instance ()->insert (opt_startup)) //安装 { ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("insert"))); return -1; } return 0; }
if (opt_remove && !opt_install) { if (-1 == SERVICE::instance ()->remove ()) //卸载 { ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("remove"))); return -1; } return 0; }
if (opt_start && opt_kill) print_usage_and_die ();
if (opt_start) { if (-1 == SERVICE::instance ()->start_svc ()) //启动 { ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("start"))); return -1; } return 0; }
if (opt_kill) { if (-1 == SERVICE::instance ()->stop_svc ()) //停止 { ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("stop"))); return -1; } return 0; }
if (opt_type) { if (-1 == SERVICE::instance ()->startup (opt_startup)) //设置启动模式 { ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("set startup"))); return -1; } return 0; }
if (opt_debug) { SetConsoleCtrlHandler (&ConsoleHandler, 1); //添加(或删除)应用程序处理函数列表 SERVICE::instance ()->svc (); //调试运行 } else { ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("%T (%t): Starting service.\n")));
ACE_NT_SERVICE_RUN(Beeper, SERVICE::instance (), ret); if (ret == 0) ACE_ERROR ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("Couldn't start service"))); else ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("%T (%t): Service stopped.\n"))); }
return 0; }
|
4)、实现main主函数入口
int ACE_TMAIN (int argc, ACE_TCHAR* argv[]) { return PROCESS::instance ()->run (argc, argv); } |
到此,基于ACE实现的服务程序就编写完了,倘若编译生成的二进制文件为TestService.exe,那么就可以通过如下命令:
安装服务:TestService.exe –i
卸载服务:TestService.exe –r
启动服务:TestService.exe –s
停止服务:TestService.exe –k
分析:
对于ACE_NT_Service类是如何封装的Windows服务的编程框架,请参见NT_Service.cpp、NT_Service.h、NT_Service.inl等几个文件:
(1)宏ACE_NT_SERVICE_DEFINE中实现了ServiceMain()和ServiceCtrlHandler()两个函数的函数体部分,同时在ServiceCtrlHandler()中还调用了RegisterServiceCtrlHandler函数来注册服务管理器的入口函数。
(2)宏ACE_NT_SERVICE_RUN中实现了服务信息表SERVICE_TABLE_ENTRYServiceTable[2]的定义,并调用StartServiceCtrlDispatcher函数来连接程序主线程到服务控制管理程序。