zz: C++後臺服務程序開發模式

    一直感受 VC++ 太複雜了,但昨天看了汪蒲陽編著的因特網應用編程,其中寫到後臺服務程序的編寫,論述的很是詳細,並且邏輯清晰,看了以後感受明白很多,故拿來與須要之人共享,並更正了原程序的一些錯誤,補充了一些材料。另外還有一種用 C++ 編寫後臺服務程序的思路(不算 .NET 上服務程序開發模型) , 之後整理好了再發上來。     在2000/XP等基於NT 的操做系統中,有一個服務管理器,它管理的後臺進程被稱爲 service。 服務是一種應用程序類型,它在後臺運行,與 UNIX 後臺應用程序相似。服務應用程序一般能夠 在本地和經過網絡爲用戶提供一些功能,例如客戶端/服務器應用程序、Web 服務器、數據庫服 務器以及其餘基於服務器的應用程序。     後臺服務 程序是在後臺悄悄運行的。咱們經過將本身的程序登記爲服務,可使本身的程序不出現 在任務管理器中,而且隨系統啓動而最早運行,隨系統關閉而最後中止。           服務控制管理器是一個RPC 服務器,它顯露了一組應用編程接口,程序員能夠方便的編寫程序來配置 服務和控制遠程服務器中服務程序。      服務程序一般編寫成控制檯類型的應用程序,總的來講,一個遵照服務控制管理程序接口要求的程序 包含下面三個函數: 1。服務程序主函數(main):調用系統函數 StartServiceCtrlDispatcher 鏈接程序主線程到服務控制管理程序。 2。服務入口點函數(ServiceMain):執行服務初始化任務,同時執行多個服務的服務進程有多個服務入口函數。 3。控制服務處理程序函數(Handler):在服務程序收到控制請求時由控制分發線程引用。(此處是Service_Ctrl)。  另外在系統運行此服務以前須要安裝登記服務程序:installService 函數。刪除服務程序則須要先刪除服務安裝登記:removeService 函數。   服務類型:
類型 說明
SERVICE_FILE_SYSTEM_DRIVER=2 文件系統驅動服務。
SERVICE_KERNEL_DRIVER=1 驅動服務。
SERVICE_WIN32_OWN_PROCESS=16 獨佔一個進程的服務。
SERVICE_WIN32_SHARE_PROCESS=32 與其餘服務共享一個進程的服務。

新建WIN32控制檯程序, 其源文件名爲service.cpp 。我用的開發工具是VC++.NET。  1.服務程序主函數     服 務控制管理程序啓動服務程序後,等待服務程序主函數調用系統函StartServiceCtrlDispatcher。一個 SERVICE_WIN32_OWN_PROCESS 類型的服務應該當即調用 StartServiceCtrlDispatcher  函數,能夠在服務啓動後讓服務入口點函數完成初始化工做。對於 SERVICE_WIN32_OWN_PROCESS  類型的服務和程序中全部服務共同的初始化工做能夠在主函數中完成,但不要超過30秒。不然必須創建另外的線程完成這些共同的初始化工做,從而保證服務程序 主函數能及時地調用 StartServiceCtrlDispatcher 函數。     主函數處理了三中命令行參數:- install,- remove,- debug,分別用於安裝,刪除和調試服務程序。若是不帶參數運行,則認爲是服務控制管理出現啓動該服務程序。參數不正確則給出提示信息。   StartServiceCtrlDispatcher 函數負責把程序主線程鏈接到服務控制管理程序。具體描述以下: BOOL StartServiceCtrlDispatcher(   const LPSERVICE_TABLE_ENTRY lpServiceTable); lpServiceStartTable  指向 SERVICE_TABLE_ENTRY 結構類型的數組,他包含了調用進程所提供的每一個服務的入口函數和字符串名。表中的最後一個元素必須爲  NULL,指明入口表結束。SERVICE_TABLE_ENTRY 結構具體描述以下:   typedef struct _SERVICE_TABLE_ENTRY {  LPTSTR lpServiceName;  LPSERVICE_MAIN_FUNCTION lpServiceProc; } SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;   lpServiceName 是一個以 NULL 結尾的字符串,標識服務名。若是是 SERVICE_WIN32_OWN_PROCESS 類型的服務,這個字符串會被忽略。 lpServiceProc 指向服務入口點函數。  //服務程序主函數。

#include "stdafx.h"

#include "Windows.h"

#define SZAPPNAME            "serverSample"         //服務程序名

#define SZSERVICENAME    "serviceSample"        //標識服務的內部名

    

//內部變量

bool                                     bDebugServer=false;

SERVICE_STATUS                            ssStatus;

SERVICE_STATUS_HANDLE    sshStatusHandle;

DWORD                                             dwErr=0;

TCHAR                                             szErr[256];

    

//下面的函數由程序實現

void    WINAPI    Service_Main(DWORD dwArgc, LPTSTR *lpszArgv);

void    WINAPI    Service_Ctrl(DWORD dwCtrlCode);

void installService();

void removeService();

void debugService(int argc,char** argv);

bool ReportStatusToSCMgr(DWORD dwCurrentState,DWORD dwWin32ExitCode,DWORD dwWaitHint);

void AddToMessageLog(LPTSTR lpszMsg);

    

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]=="/")))

         {

                 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 函數。

                            printf("%s - install to install the service \n",SZAPPNAME);

                            printf("%s - remove to remove the service \n",SZAPPNAME);

                            printf("%s - debug to debug the service \n",SZAPPNAME);

                            printf("\n StartServiceCtrlDispatcher being called.\n");

                            printf("This may take several seconds.Please wait.\n");

                            if(!StartServiceCtrlDispatcher(dispatchTable))

                                     AddToMessageLog(TEXT("StartServiceCtrlDispatcher failed."));

                            else

                                     AddToMessageLog(TEXT("StartServiceCtrlDispatcher OK."));

                 }

                 exit(0);

         }

         return 0;

}  2.服務入口點函數 服務入口點函數 service_main 首先調用系統函數 RegisterServiceCtrlHandler 註冊服務控制處理函數 service_ctrl,而後調用 ReportStatusToSCMgr 函數,它經過系統函數 SetServiceStatus 更新服務的狀態,而後調用特定的服務初始化入口函數 ServiceStart 完成具體的初始化工做。  //服務入口點函數

void ServiceStart(DWORD dwArgc,LPTSTR* lpszArgv);//具體服務的初始化入口函數
void    WINAPI    Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)

{

         //註冊服務控制處理函數

         sshStatusHandle=RegisterServiceCtrlHandler(TEXT(SZSERVICENAME),Service_Ctrl);

         //若是註冊失敗

         if(!sshStatusHandle)

         {

                 goto cleanup;

                 return;

         }

         //初始化 SERVICE_STATUS 結構中的成員

         ssStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS;

         ssStatus.dwServiceSpecificExitCode=0;

         //更新服務狀態

         if(!ReportStatusToSCMgr(

                 SERVICE_START_PENDING,//服務狀態,The service is starting.

                 NO_ERROR,                        //退出碼                    

                 3000))                                     //等待時間

                 goto cleanup;                //更新服務狀態失敗則轉向 cleanup

         ServiceStart(dwArgc,lpszArgv);

         return;

cleanup:

         //把服務狀態更新爲 SERVICE_STOPPED,並退出。

         if(sshStatusHandle)

                 (void)ReportStatusToSCMgr(SERVICE_STOPPED,dwErr,0);

} 序函數     函數 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();         //由具體的服務程序實現

                 return;

                 //暫停服務

         case SERVICE_CONTROL_PAUSE:

                 ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);

                 ServicePause();        //由具體的服務程序實現

                 ssStatus.dwCurrentState=SERVICE_PAUSED;

                 return;

                 //繼續服務

         case SERVICE_CONTROL_CONTINUE:

                 ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);

                 ServiceContinue(); //由具體的服務程序實現

                 ssStatus.dwCurrentState=SERVICE_RUNNING;

                 return;

                 //更新服務狀態

         case SERVICE_CONTROL_INTERROGATE:

                 break;

                 //無效控制碼

         default:

                 break;

         }

         ReportStatusToSCMgr(ssStatus.dwCurrentState,NO_ERROR,0);

} 除了系統定義的五種控制碼外(還有一種是:SERVICE_CONTROL_SHUTDOWN), 用戶還可自定義控制碼,其取值範圍是128-255。用戶能夠經過控制面板中的服務項向特定服務程序的控制處理函數發送控制碼,程序員能夠調用系統函數  ControlService 直接向服務程序的控制處理函數發送控制碼。其函數原型以下:   BOOL ControlService(   SC_HANDLE hService,   DWORD dwControl,   LPSERVICE_STATUS lpServiceStatus ); hService :函數 OpenService or CreateService 返回的服務程序句柄。 dwControl :控制碼,不能是SERVICE_CONTROL_SHUTDOWN。 lpServiceStatus:返回最後收到的服務狀態信息。   4.安裝服務程序       每一個已安裝服務程序在  HKEY_LOCAL_MACHINE\SYSTE\CurrentControlSet\Services  下都有一個服務名的關鍵字,程序員能夠調用系統函數 CreateService  安裝服務程序,並指定服務類型,服務名等。這個函數建立一個服務對象,並將其增長到相關的服務控制管理器數據庫中。 下面是函數原型:   SC_HANDLE CreateService(   SC_HANDLE hSCManager, //服務控制管理程序維護的登記數據庫的句柄,由系統函數OpenSCManager 返回   LPCTSTR lpServiceName, //以NULL 結尾的服務名,用於建立登記數據庫中的關鍵字   LPCTSTR lpDisplayName, //以NULL 結尾的服務名,用於用戶界面標識服務   DWORD dwDesiredAccess, //指定服務返回類型   DWORD dwServiceType, //指定服務類型   DWORD dwStartType, //指定什麼時候啓動服務   DWORD dwErrorControl, //指定服務啓動失敗的嚴重程度   LPCTSTR lpBinaryPathName, //指定服務程序二進制文件的路徑   LPCTSTR lpLoadOrderGroup, //指定順序裝入的服務組名   LPDWORD lpdwTagId, //忽略,NULL   LPCTSTR lpDependencies, //指定啓動該服務前必須先啓動的服務或服務組   LPCTSTR lpServiceStartName, //以NULL 結尾的字符串,指定服務賬號。如是NULL,則表示使用LocalSystem 賬號   LPCTSTR lpPassword //以NULL 結尾的字符串,指定對應的口令。爲NULL表示無口令。但使用LocalSystem時填NULL );        對於一個已安裝的服務程序,能夠調用系統函數 OpenService 來獲取服務程序的句柄 下面是其函數原型: SC_HANDLE OpenService(   SC_HANDLE hSCManager,   LPCTSTR lpServiceName,   DWORD dwDesiredAccess );   hSCManager :服務控制管理程序微服的登記數據庫的句柄。由函數 OpenSCManager function 返回 這個句柄。 lpServiceName :將要打開的以NULL 結尾的服務程序的名字,和 CreateService  中的 lpServiceName 相對應。 dwDesiredAccess :指定服務的訪問類型。服務響應請求時,首先檢查訪問類型。 用CreateService 或OpenService 打開的服務程序句柄使用完畢後必須用CloseServiceHandle 關閉。 OpenSCManager打開的服務管理數據庫句柄也必須用它來關閉。  //安裝服務程序

void installService()

{

         SC_HANDLE schService;

         SC_HANDLE schSCManager;

         TCHAR szPath[512];

         //獲得程序磁盤文件的路徑

         if(GetModuleFileName(NULL,szPath,512)==0)

         {

                 _tprintf(TEXT("Unable to install %s - %s \n"),

                            TEXT(SZAPPNAME),

                 GetLastError());//@1獲取調用函數返回的最後錯誤碼

                 return;

         }

         //打開服務管理數據庫

         schSCManager=OpenSCManager(

                                                        NULL,        //本地計算機

                                                        NULL,        //默認的數據庫

                                                        SC_MANAGER_ALL_ACCESS    //要求全部的訪問權

                                                        );

if(schSCManager)

         {

                 //登記服務程序

                 schService=CreateService(

                            schSCManager,                                        //服務管理數據庫句柄

                            TEXT(SZSERVICENAME),                         //服務名

                            TEXT(SZAPPNAME),             //用於顯示服務的標識

                            SERVICE_ALL_ACCESS,                            //響應全部的訪問請求

                            SERVICE_WIN32_OWN_PROCESS,             //服務類型

                            SERVICE_DEMAND_START,                        //啓動類型

                            SERVICE_ERROR_NORMAL,                        //錯誤控制類型

                            szPath,                                                            //服務程序磁盤文件的路徑

                            NULL,                                                                //服務不屬於任何組

                            NULL,                                                                //沒有tag標識符

                            NULL,                            //啓動服務所依賴的服務或服務組,這裏僅僅是一個空字符串

                            NULL,                                                                //LocalSystem 賬號

                            NULL);

                 if(schService)

                 {

                            _tprintf(TEXT("%s installed. \n"),TEXT(SZAPPNAME));

                            CloseServiceHandle(schService);

                 }

                 else

                 {

                            _tprintf(TEXT("CreateService failed - %s \n"),GetLastError());

                 }

                 CloseServiceHandle(schSCManager);

         }

         else

                 _tprintf(TEXT("OpenSCManager failed - %s \n"),GetLastError());

}
程序員

相關文章
相關標籤/搜索