郁闷阳光的专栏

欢迎朋友们经常来逛逛,一起交流

开发Windows NT的后台服务

 摘 要:利用一组WIN32 API函数将自主开发的服务器程序扩展为NT的一项后台服务,让NT把其当作系统服务自动加载,从而扩充NT服务器的后台功能,并结合一 个实例说明开发中应做的工作。
  关键词:Windows NT 后台服务 服务控制管理器

  在WINDOWS NT服务器中,NT的一些后台系统服务随着NT的启动而自动加载, 不需用户人工 干预,这种方式不仅简化了服务器启动过程,同时也使一些重要服务的启动更加安全可靠。 而且,在服务器启动以后,用户也可以利用控制面板中服务控制面板应用程序灵活地设置这 些后台服务的启动属性,方便了用户对NT后台服务的管理。笔者在开发基于NT服务器的应用 程 序时,发现利用一组WIN32 API 函数完全可以将自己开发的服务器程序扩展为NT的一项后台 服务,让NT把其当作系统服务自动加载,从而扩充NT服务器的后台服务功能。下面结合一个 实例,概述开发NT后台服务应用程序的方法和步骤。

1 自定义后台服务注册
  在NT操作系统中,所有的后台服务全都由服务控制管理器进行统一管理,这些后台服务的状 态数据都保存在服务控制管理器数据库中。所以要想创建一个新的后台服务,在应用程序的 主模块里应首先调用函数OpenSCManager打开该数据库,再调用函数CreateService在此数据 库中创建1个新的服务线程对象,并将该线程对象启动属性设置为随系统启动自动加载,这 样NT在重新启动时该线程就会由NT自动启动。完成这一步,仅仅实现了后台服务线程对象的 注册,还没有建立与服务控制管理器的联结。

//在服务控制管理器数据库中注册后台服务线程

void CmdRegisterService()
{
  SCHANDLE  schService;
  SCHANDLE  schSCManager;
  TCHAR szPath[512];
  //获得后台服务进程执行路径
  if(GetModuleFileName(NULL,szPath,512)==0)
  {tprintf(TEXT(“Unable to install %s - %s\n"),
  TEXT(“Service Example"), GetLastErrorText(szErr, 256));
   return;
  }
  //打开服务控制管理器数据库
  schSCManager=OpenSCManager(
         NULL,   //本地机器
         NULL,   //默认数据库
         SCMANAGERALLACCESS //访问权限
         );
  //在此数据库中创建后台服务线程对象
  if(schSCManager)
  { schService=CreateService(
    schSCManager, //服务控制管理器数据库
    TEXT(“ServiceExample"), //后台服务名称
    TEXT(“Service Example"),//在服务控制面板中显示的//服务名称
    SERVICE_ALL_ACCESS, //访问权限
    SERVICE_WIN32_OWN_PROCESS, //服务类型
    SERVICE_AUTO_START, //启动类型,随系统自动//加载
    SERVICE_ERROR_NORMAL,
    szPath,
    NULL,
    NULL,
TEXT(“"),
NULL,  //本地系统帐号
NULL);// 没有口令
  //在这里可以创建多个后台服务线程对象,完成不同的
//后台任务
  if(schService)
   {tprintf(TEXT(“%s installed.\n"),TEXT(“Service Example")); 
    CloseServiceHandle(schService);
   }
   else
   {tprintf(TEXT(“CreateService failed-%s\n"),
   GetLastErrorText(szErr, 256));
   }
   CloseServiceHandle(schSCManager);
  }
  else
   tprintf(TEXT(“OpenSCManager failed - %s\n"),
GetLastErrorText(szErr,256));
}


2 定义后台服务线程入口点ServiceMain
  在此之前应该首先定义服务线程入口表(SERVICETABLEENTRY),它记录 了服务器应用程序 要执行的所有后台服务的入口点(在1个服务器应用程序中可同时加载多个后台服务线程)。 每一个后台服务线程都有自己的ServiceMain执行入口点。当服务控制管理器接收到启动后 台服务的命令后,它会首先将启动命令发送到服务控制分配器中,然后,服务控制分配器就 会按照服务线程入口表定义的对应关系启动1个新线程去执行指定的后台服务。在Servic eMain中应当调用RegisterServiceCtrlHandler先注册1个服务控制函数Handler,它负责接 收和处理服务控制管理器发出的命令,并通过调用SetServiceStatus重新设置后台服务状态 ,将被启动的后台服务状态信息回送到服务控制管理器中。这些操作主要是在后台服务线程 启动之前完成一些必要的初始化工作。ServiceMain及Handler是占位符,在程序中可以用不 同的名称定义自己的后台服务线程入口函数和服务控制处理函数。

//定义后台服务线程入口表
SERVICETABLEENTRY MyServiceTable[]=
{
   {
   TEXT(“ServiceExample"),//后台服务线程的名称
   (LPSERVICEMAINFUNCTION)MyServiceMain//后台服//务线程入口点
   },
   {NULL, NULL }   //标志表的结束
};
//服务线程入口函数
void WINAPI MyServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
  //注册服务控制处理函数
sshStatusHandle=RegisterServiceCtrlHandler(TEXT(“ServiceExample"), Servi ceControlHandler);
  if(!sshStatusHandle)
goto cleanup;
  //服务类型
  ssStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; 
  //指定的出错代码
  ssStatus.dwServiceSpecificExitCode=0;
  //启动自己定义的后台服务
  ServiceStart();
cleanup:
  if(sshStatusHandle)
(VOID)ReportStatusToSCMgr(
SERVICESTOPPED,dwErr,0);
  return;
}

//服务控制处理函数:负责接收和处理服务控制管理器发出的
//命令
VOID WINAPI ServiceControlHandler(DWORD dwCtrlCode)
{
  switch(dwCtrlCode)
  {
   //停止服务之前,应首先向服务控制管理器回送状态信息
   case SERVICE_CONTROLSTOP:
    ReportStatusToSCMgr(SERVICE_STOP_PENDING,NOE RROR, 0);
    ServiceStop();
    return;
   //更新服务状态
   case SERVICE_CONTROL_INTERROGATE:
    break;
   default:
    break;
  }
  //向服务控制面板管理器回送状态信息
  ReportStatusToSCMgr(ssStatus.dwCurrentState, NOERROR, 0); 
}

//设置当前服务状态并将状态信息回送到服务控制管理器
BOOL ReportStatusToSCMgr(DWORD dwCurrentState,DWORD
dwWin32ExitCode,WORD dwWaitHint)
{
  static DWORD dwCheckPoint=1;
  BOOL fResult=TRUE;
  if(dwCurrentState==SERVICESTARTPENDING)
   ssStatus.dwControlsAccepted=0;
  else
   ssStatus.dwControlsAccepted=SERVICE_ACCEPT_STOP;
  //设置状态信息
  ssStatus.dwCurrentState=dwCurrentState;
  ssStatus.dwWin32ExitCode=dwWin32ExitCode;
  ssStatus.dwWaitHint=dwWaitHint;
  if((dwCurrentState==SERVICE_RUNNING )‖
   (dwCurrentState==SERVICE_STOPPED))
   ssStatus.dwCheckPoint=0;
  else
   ssStatus.dwCheckPoint=dwCheckPoint++;
  //将状态信息回送到服务控制管理器
  if(!(fResult=SetServiceStatus(sshStatusHandle,&ssStatus))){
   AddToMessageLog(TEXT(“SetServiceStatus"));//向NT事//件管理器报告出错消息
   }
   return fResult;
}


3 建立应用程序主线程与服务控制管理器的连接
  在创建了服务器应用程序的所有服务线程对象后,当NT重新启动加载该服务器进程或者从服 务控制面板中手工启动该服务进程时, 服务器应用程序主线程就会调用函数StartServiceCt rlDispatcher建立服务进程主线程与服务控制管理器的连接,使得主线程扮演起服务控制分 配器的角色,只有当所有的服务线程终止后,该主线程才会完成任务并返回。利用上述过程 建立起来的连接,服务控制管理器会将服务控制面板中用户对服务进程的控制命令(Start、 Terminate等)发送到主线程中,交给服务控制分配器处理。

//服务进程入口
voidCRTAPI1 main(int argc, char **argv)
{
  if((argc>1)&&((*argv[1]==‘-')‖(*argv[1]==‘/')) )
  {
   if(stricmp(“register", argv[1]+1)==0)
    CmdRegisterService(); //注册后台服务线程对象
   else
    goto dispatch;
   exit(0);
  }
  dispatch:
   //启动服务控制分配器,建立主线程与服务控制面板的
//连接
   if(StartServiceCtrlDispatcher(MyServiceTable))
    AddToMessageLog(TEXT (“StartServiceCtrlDispatcher success."));
}

4 定义自己的后台服务实现主模块
  该模块放在服务线程入口点ServiceMain中,在服务线程初始化完毕以后,就可以启动自己 定义的后台服务。  

void ServiceStart()
{
  //向服务控制管理器报告服务正在启动
  if(!ReportStatusToSCMgr(
SERVICE_START_PENDING, //service state
NOERROR,
dwWaitHint))
return;
  //下面是线程的初始化代码(注意:初始化代码执行时间
//不应超过设定的nWaitHi nt,否则服务控制管理器会认
//为服务线程已经没有响应
  //向服务控制管理器报告服务已经启动
  if(!ReportStatusToSCMgr(
SERVICE_RUNNING,  //service state
NOERROR,   //exit code
0))    //wait hint
   return;
//开始执行服务
}
//停止后台服务线程
void ServiceStop()
{
  //添加结束服务线程代码
}


  在该示例中,后台服务线程在服务器上创建1个网络套接字,并在6002端口监听TCP/IP客 户 的连接请求。如果与新的客户建立连接后,首先向其发送欢迎消息,接下来,只要每收到客 户发送过来的消息,就会向其回送1个确认信息,直到连接断开以后重新进入监听状态。感 兴趣的读者可以将本程序在VC5.0中编译成可执行文件(在AppWizard中选择Win32 Console A pplication,工程名为ntservice),然后在WINDOWS NT SERVER4.0的控制台中输入“ntservi ce -register”,再重新启动服务器,打开控制面板中的服务管理器,就可以看到Service Example已启动。如果要测试服务例程的响应功能,可以自己编写1个TCP/IP客户程序,并连 接 到该服务器的端口6002。本程序在VC5.0中编译连接,在中文WINDOWS NT SERVER4.0中调试 通过。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭