Windows后台服务程序编写
1. 为什么要编写后台服务程序
工作中有一个程序需要写成后台服务的形式,摸索了一下,跟大家分享。
在windows操作系统中后台进程被称为 service。 服务是一种应用程序类型,它在后台运行,通常没有交互界面。服务应用程序通常可以 在本地和通过网络为用户提供一些功能,是为其它进程服务的。通过将程序注册为服务,可以使自己的程序随系统启动而最先运行,随系统关闭而最后停止。也可以windows服务管理器手动控制服务的启动、关闭。
2. 编写后台服务程序步骤
Windows提供了一套后台服务程序编程接口,用户在编写后台服务程序时需要遵循一定的编程框架,否则服务程序不能正常运行。
服务程序通常编写成控制台类型的应用程序,总的来说,一个遵守服务控制管理程序接口要求的程序 包含下面三个函数:
1)服务程序主函数(main):调用系统函数 StartServiceCtrlDispatcher 连接程序主线程到服务控制管理程序。
和其它进程一样,Main函数是服务进程的入口函数,服务控制管理器(SCM)在启动服务程序时,会从服务程序的main函数开始执行。在进入点函数里面要完成ServiceMain的初始化,准确点说是初始化一个SERVICE_TABLE_ENTRY结构数组,这个结构记录了这个服务程序里面所包含的所有服务的名称和服务的进入点函数。然后再调用接口StartServiceCtrlDispatcher 。
Main函数的函数框架如下:
int _tmain(int argc, _TCHAR* argv[])
{
//服务入口点函数表
SERVICE_TABLE_ENTRY dispatchTable[]=
{
{TEXT(SZSERVICENAME),(LPSERVICE_MAIN_FUNCTION)Service_Main},
{ NULL,NULL}
};
if((argc>1)&&((*argv[1]==‘-‘)||(argv[1]=="/")))
{
/*
参数个数大于1是安装或者删除服务,该操作是由用户来执行的
当然也可以讲这一部分功能另写一个程序来实现
*/
if(_stricmp("install",argv[1]+1)==0)
{
installService();
}
else if(_stricmp("remove",argv[1]+1)==0)
{
removeService();
}
else if(_stricmp("debug",argv[1]+1)==0)
{
bDebugServer=true;
debugService(argc,argv);
}
}
else
{
/*
如果未能和上面的如何参数匹配,则可能是服务控制管理程序来启动该程序。 立即调用StartServiceCtrlDispatcher 函数
*/
g_logout.Logout("%s\n", "enter StartServiceCtrlDispatcher...");
//通知服务管理器为每一个服务创建服务线程
if(!StartServiceCtrlDispatcher(dispatchTable))
g_logout.Logout("%s\n", "StartServiceCtrlDispatcher failed.");
else
g_logout.Logout("%s\n", "StartServiceCtrlDispatcher OK.");
}
return 0;
}
SCM启动一个服务程序之后,它会等待该程序的主线程去调StartServiceCtrlDispatcher。如果那个函数在两分钟内没有被调用,SCM将会认为这个服务有问题,并调用TerminateProcess去杀死这个进程。这就要求你的主线程要尽可能快的调用StartServiceCtrlDispatcher。
2)服务入口点函数(ServiceMain):执行服务初始化任务,同时执行多个服务的服务进程有多个服务入口函数。
在服务入口函数里,必须立即注册服务控制回调函数。然后调用函数SetServiceStatus 通知SCM 服务现在的状态,否则SCM会认为服务启动失败。
ServiceMain函数框架如下:
void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)
{
//注册服务控制处理函数
sshStatusHandle=RegisterServiceCtrlHandler(TEXT(SZSERVICENAME),Service_Ctrl);
//如果注册失败
if(!sshStatusHandle)
{
g_logout.Logout("%s\n", "RegisterServiceCtrlHandler failed...");
return;
}
//初始化 SERVICE_STATUS 结构中的成员
ssStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; //可执行文件中只有一个单独的服务
ssStatus.dwServiceSpecificExitCode=0;
ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; //允许用SCP去停止该服务
//更新服务状态
if(ReportStatusToSCMgr(SERVICE_START_PENDING,//服务状态,服务仍在初始化
NO_ERROR,
3000)) //等待时间
SvcInit( dwArgc, lpszArgv ); //服务初始化函数
else
g_logout.Logout("%s\n", "ReportStatusToSCMgr SERVICE_START_PENDING failed...");
}
服务初始化函数SvcInit:
该函数的写法比较重要。在函数中创建一个等待事件,然后一直等待该事件。该线程在服务接到请求之前一直处于挂起状态,直到接到服务停止消息。
VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
{
/*创建事件*/
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if ( ghSvcStopEvent == NULL)
{
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
// Report running status when initialization is complete.
ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
// 在这里执行服务线程的创建...
while(1)
{
// 等待停止事件被触发
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
}
3)控制服务处理程序函数(Handler):在服务程序收到控制请求时由控制分发线程引用。(此处是Service_Ctrl)。
void WINAPI Service_Ctrl(DWORD dwCtrlCode)
{
//处理控制请求码
switch(dwCtrlCode)
{
//先更新服务状态为 SERVICDE_STOP_PENDING,再停止服务。
case SERVICE_CONTROL_STOP:
ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);
ServiceStop(); //由具体的服务程序实现
/*ssStatus.dwCurrentState=SERVICE_STOPPED;*/
//其它控制请求...
default:
break;
}
}
3. 注意事项
1)安装服务可以另写一个程序,也可以并在服务程序当中,比较简单,这里就不介绍了。
2)服务初始化函数SvcInit里创建等待事件比较重要,在服务接收到服务停止消息后,触发该事件。
3)Service_Main在等待事件触发后立即返回,服务进程就会退出了。