前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你实现Windows服务

手把手教你实现Windows服务

作者头像
河边一枝柳
发布2021-08-06 14:58:15
9120
发布2021-08-06 14:58:15
举报

你是否想过要实现一个Windows程序,可以让它在系统启动的时候自动运行?或者后台运行,不显示界面?或者希望运行的时候能够方便的指定权限?那么Windows服务可以满足你的需求。

本文主要介绍如何用C++编写Windows服务。根据以下三点进行讲解:

  1. Windows服务是如何管理和运行的?
  2. 如何实现Windows服务?
  3. 如何配置Windows服务?

Windows服务控制管理器

在Widnows Service本地管理可以通过命令行services.msc打开,可以看到Service的名称,运行状态等,也可以对Service 进行停止启动等操作。

Windows控制服务管理器(Service Control Manager)主要负责统一的管理Windows Service,比如:

  • 记录和维护安全的Service。这些信息会记录在注册表中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
  • 当系统启动的时候会按照设定的顺序启动Windows服务,比如服务的Group Order和服务的依赖关系
  • 我们自己编写的Service进程和SCM主要通过RPC进行交互,比如SCM像我们的Service进程发送StartStop等请求;Service程序也会告诉SCM当前Service的状态;对于我们的程序来说不一定关系这个底层实现细节,只需要知道我们调用的Service函数底层是和SCM进行了通信。Service常见的状态如下图所示。(图片来自于微软MSDN Service State Transitions)

实现Windows服务

接下来我们使用C++来实现一个Windows服务。我们先来看一下实现的结构图:

在介绍代码编写之前,我们先定义一个数据结构,实例化一个全局变量,后续会用到,代码里均以加上注释。

代码语言:javascript
复制
struct ServiceContext
{
  ServiceContext() = delete;
  ServiceContext(std::string strSvcName) : m_strSvcName(strSvcName)
  {
    ;
  }
  ~ServiceContext()
  {
    if (m_hStartedEvent)
      CloseHandle(m_hStartedEvent);

    if (m_hStopEvent)
      CloseHandle(m_hStopEvent);
  }
  std::string           m_strSvcName;
  SERVICE_STATUS_HANDLE m_svcStatusHandle = nullptr; //Service Status Handle
  SERVICE_STATUS        m_svcStatus;                 //Service Name

  //This event used to check whether service worker is started
  HANDLE                m_hStartedEvent = CreateEvent(
    NULL,    // default security attributes
    TRUE,    // manual reset event
    FALSE,   // not signaled
    NULL);   // no name;   

  //This event used to notify service worker to stop
  HANDLE                m_hStopEvent = CreateEvent(
    NULL,    // default security attributes
    TRUE,    // manual reset event
    FALSE,   // not signaled
    NULL);   // no name;      
  DWORD                 m_dwHintTimeout = 5 * 1000;  // 5 seconds, check interval for starting and stopping
  DWORD                 m_dwCheckPoint = 1;          // It's used to co-work check for starting and stopping
};

ServiceContext gCoderSvcCtx("CoderService");

第一步 当用户在服务管理器中启动Service的时候,Service Control Manager(后面简称SCM), 将会创建我们编写的服务进程。此时进程会调用main

  • StartServiceCtrlDispatchermain函数启动后,尽量快速调用,否则一段时间后,SCM会认为程序hang住了,而关闭掉Service进程。
  • StartServiceCtrlDispatcher, 注册我们的Service: Service名称为CoderService, Service对应的启动函数是CoderServiceMain
  • StartServiceCtrlDispatcher一般当Service设置为停止状态的时候,才会退出。当然这里的停止包括: Service进程终止; Service接收到停止指令,后设置Stop状态等等。
  • 通过代码不难发现,一个服务程序可以注册多个Service,不过本文主要讲解单独Service实现。
代码语言:javascript
复制
int main()
{
  SERVICE_TABLE_ENTRY svcTableEntry[] = {
    {const_cast<char *>(gCoderSvcCtx.m_strSvcName.c_str()), CoderServiceMain},
    {NULL, NULL}
  };

  //It should be invoked in main as soon as possible
  //Start failed, you can use GetLastError() to get the error code
  if (!StartServiceCtrlDispatcher(svcTableEntry))
    return -1;

  return 0;
}

第二步 CoderServiceMain是对应Service CoderWorker的回调程序入口。

  • 注册了CoderServiceController作为Event处理回调函数,比如用户停止服务。
  • SERVICE_WIN32_OWN_PROCESS设置服务为独占程序的服务,即多个Sevice共享服务程序。
  • 设置服务进入状态SERVICE_START_PENDING, 通知SCM
  • 调用StartCoderServiceWorker 进入下一步启动过程
  • ReportSvcStatusSetServiceStatus的封装,去除重复代码调用
代码语言:javascript
复制
//Refer to: https://docs.microsoft.com/en-us/windows/win32/services/writing-a-servicemain-function
bool ReportSvcStatus(ServiceContext& ctx,
  DWORD dwCurrentState,
  DWORD dwWin32ExitCode)
{
  // Fill in the SERVICE_STATUS structure.
  ctx.m_svcStatus.dwCurrentState = dwCurrentState;
  ctx.m_svcStatus.dwWin32ExitCode = dwWin32ExitCode;
  ctx.m_svcStatus.dwWaitHint = ctx.m_dwHintTimeout;

  if (dwCurrentState == SERVICE_START_PENDING)
    ctx.m_svcStatus.dwControlsAccepted = 0;
  else
    ctx.m_svcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;

  if ((dwCurrentState == SERVICE_RUNNING) ||
    (dwCurrentState == SERVICE_STOPPED))
    ctx.m_svcStatus.dwCheckPoint = 0;
  else 
    ctx.m_svcStatus.dwCheckPoint = ctx.m_dwCheckPoint++;

  // Report the status of the service to the SCM.
  return SetServiceStatus(ctx.m_svcStatusHandle, &ctx.m_svcStatus);
}

//Each Service binding a ServiceMain
void CoderServiceMain(DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors)
{
  // 1. Register Control Handler
  gCoderSvcCtx.m_svcStatusHandle = RegisterServiceCtrlHandler(const_cast<char *>(gCoderSvcCtx.m_strSvcName.c_str()),
    CoderServiceController);

  if (!gCoderSvcCtx.m_svcStatusHandle)
    return;

  // 2.  Set SERVICE_START_PENDING
  gCoderSvcCtx.m_svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  gCoderSvcCtx.m_svcStatus.dwServiceSpecificExitCode = 0;

  if (!ReportSvcStatus(gCoderSvcCtx, SERVICE_START_PENDING, NO_ERROR))
  {
    ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
    return;
  }

  // 3. your worker start
  // Service woker will work for specified task, if it return means it's stopped
  StartCoderServiceWorker(dwNumServicesArgs, lpServiceArgVectors);
  return;
}

第三步 StartCoderServiceWorker 这个是Service启动的主要过程

  • CoderServiceWorker 你可以这样理解:StartCoderServiceWorker以及之前的部分,主要是为了和SCM进行交互,而CoderServiceWorker则为你的Service需要执行的内容/工作。
  • 大家在实现一些工作/任务的时候都会做一些封装,而这里CoderWorker也是对实际执行的内容/工作做了封装。提供了两个接口Start, Stop。划重点了,如果你要实现一个windows服务的具体工作,那么只需要在Run函数中做实现即可。
  • CoderServiceWorker的初始化完成后,会触发m_hStartedEventStartCoderServiceWorker收到这个事件后,会通知SCM状态改为SERVICE_RUNNING。因为有些程序初始化需要时间比较长,不过一般是不建议初始化时间太长。有的程序员会让程序的Service尽快进入正在运行状态,异步的去做一些初始化,但这样会导致一个问题,就是服务已经显示正在运行状态,但其初始化未完成无法提供服务;所以有的程序员会等待初始化完毕后,才通知SCM为正在运行状态,以为这样保证,服务正在运行状态就保证了其已经可以正常工作了。这两种方式根据自己的场景进行抉择。而本程序是用sleep模拟了10秒钟的初始化时间,并且在StartCoderServiceWorker等待m_hStartedEvent。如果初始化一直没有完成,那么StartCoderServiceWorker会间隔m_dwHintTimeout像SCM发送SERVICE_START_PENDING。为什么要这么做?因为MSDN上说如果在m_dwHintTimeout间隔时间后需要更新服务的状态,否则有可能SCM会认为程序已经出错,并且停止服务(但本人实际测试,并不会停止服务,但为了信任微软的文档,此程序还是会及时的发送消息给SCM)。

关于m_hStopEvent这个和m_hStartedEvent实现方式类似,主要用于感知我们的服务的工作已经停止了,可以对SCM发送SERVICE_STOPPED的状态了。这个后续再讲,因为目前说的是Service启动过程。

启动完成后程序将会进入如下状态:

  • StartCoderServiceWorker 等待m_hStopEvent事件
  • CoderServiceWorker 在等待m_hStopEvent事件
  • CoderWorker::Run循环执行任务,并且查看是否需要停止
代码语言:javascript
复制
class CoderWorker
{
public:
  bool Start()
{
    // You should fill your Worker thread init task here
    // Here just sleep 10 seconds to fake the init time
    std::this_thread::sleep_for(std::chrono::seconds(10));

    //Start the worker process tasks
    int iTmpPara = 1;
    m_thWorker = std::thread(&CoderWorker::Run, this, iTmpPara);

    return true;
  }
  void Stop()
{
    m_bStop = true;
    if (m_thWorker.joinable())
      m_thWorker.join();

    // Here we just Fake sleep 10 seconds to stop
    std::this_thread::sleep_for(std::chrono::seconds(10));
  }

  void Run(int iPara)
{
    while (!m_bStop)
    {
      //do task
      //Here just fill your code to do your work
      //just here fake sleep 1 second to do task
      std::this_thread::sleep_for(std::chrono::seconds(1));
    }
  }

private:
  std::thread   m_thWorker;
  volatile bool m_bStop = false;
};

// Here you can also specify other parameters
void CoderServiceWorker(ServiceContext& ctx, DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors)
{
  // 1. Init and Start worker and notify the it's started
  CoderWorker woker{};
  if (!woker.Start())
    ExitThread(-1);

  SetEvent(ctx.m_hStartedEvent);

  // 2. Wait for the stop event
  DWORD dwCode = WaitForSingleObject(ctx.m_hStopEvent, INFINITE);
  woker.Stop();
  ExitThread(0);
  return;
}

//Service Worker means what your service really do
void StartCoderServiceWorker(DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors)
{
  // 1. Start your service worker to handle tasks
  std::thread thWorker(CoderServiceWorker, std::ref(gCoderSvcCtx), dwNumServicesArgs, lpServiceArgVectors);
  
  // 2. Check the starting status
  while (true)
  {
    DWORD dwCode = WaitForSingleObject(gCoderSvcCtx.m_hStartedEvent, gCoderSvcCtx.m_dwHintTimeout);
    if (WAIT_TIMEOUT == dwCode)
    {
      //Still starting
      ReportSvcStatus(gCoderSvcCtx, SERVICE_START_PENDING, NO_ERROR);
    }
    else if (WAIT_OBJECT_0 == dwCode)
    {
      //Started Finish
      ResetEvent(gCoderSvcCtx.m_hStartedEvent);
      ReportSvcStatus(gCoderSvcCtx, SERVICE_RUNNING, NO_ERROR);
      break;
    }
    else
    {
      //Other errors
      ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
      return;
    }
  }
  
  // 3. Check whether receive stop event
  while (true)
  {
    DWORD dwCheckTime = 1000; //1 second
    DWORD dwCode = WaitForSingleObject(gCoderSvcCtx.m_hStopEvent, dwCheckTime);
    if (WAIT_TIMEOUT == dwCode)
    {
      //Still runining
      continue;
    }
    else if (WAIT_OBJECT_0 == dwCode)
    {
      //It begin to stop
      // run into Stop pending status
      ReportSvcStatus(gCoderSvcCtx, SERVICE_STOP_PENDING, NO_ERROR);
      break;
    }
    else
    {
      //Other errors
      ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
      return;
    }
  }

  // 4. Check the stopping status
  // If it's stopped then set the status to STOP
  HANDLE hThreadHandle = thWorker.native_handle();
  while (true)
  {
    DWORD dwCode = WaitForSingleObject(hThreadHandle, gCoderSvcCtx.m_dwHintTimeout);
    if (WAIT_TIMEOUT == dwCode)
    {
      //Still stopping
      ReportSvcStatus(gCoderSvcCtx, SERVICE_STOP_PENDING, NO_ERROR);
    }
    else if (WAIT_OBJECT_0 == dwCode)
    {
      // stop Finish
      // Reset the stop event
      ResetEvent(gCoderSvcCtx.m_hStopEvent);
      ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
      break;
    }
    else
    {
      //Other errors
      ReportSvcStatus(gCoderSvcCtx, SERVICE_STOPPED, NO_ERROR);
      return;
    }
  }
  return;
}

第四步 上面已经描述完成了程序的启动过程,并且服务已经处于正在运行状态了。那么假设这个时候用户在服务管理器中点击了停止服务,程序会如何运行呢?

  • 首先还记得第二步中提到的CoderServiceController不?这是个回调函数,收到了SERVICE_CONTROL_STOP,会先设置服务状态为SERVICE_STOP_PENDING, 并且设置m_hStopEvent通知其他线程开始停止工作。
  • CoderServiceWorker 在接受到m_hStopEvent事件,调用woker.Stop()设置m_bStoptrue,此时CoderWorker::Run,停止工作。这里模拟了10秒钟的stop后释放资源的时间。
  • StartCoderServiceWorker 接收到m_hStopEvent事件,开始等待CoderServiceWorker线程停止,并且每隔m_dwHintTimeout时长给SCM报告SERVICE_STOP_PENDING。直到CoderServiceWorker停止,给SCM报告SERVICE_STOPPED状态。
代码语言:javascript
复制
//Each Service have a Service Controller to control the 
void CoderServiceController(DWORD dwControl)
{
  // Handle the requested control code. 
  switch (dwControl)
  {
  case SERVICE_CONTROL_SHUTDOWN:
  case SERVICE_CONTROL_STOP:
    ReportSvcStatus(gCoderSvcCtx, SERVICE_STOP_PENDING, NO_ERROR);
    // Signal the service to stop.
    SetEvent(gCoderSvcCtx.m_hStopEvent);

    return;

  default:
    break;
  }
}

服务的配置

上面已经编写了一个Service程序,下面来讲讲如何对我们的服务进行部署和配置。本人知道的大概有两种:

  • 自己在Service程序中实现安装,删除服务的功能,大致是调用CreateService这类API。然后通过命令行参数来控制程序启动是创建服务删除服务,还是启动服务
  • 使用命令sc对服务进行操作,本文主要讲sc命令

安装服务

创建一个服务的命令: sc create CoderWorker binpath=C:\Personal\WindowsServiceNormal.exe type=own start=demand displayname="Coder Woker"

安装一个服务我们最需要注意的是以下几点:

  • 运行程序的账户信息: 这里没有设置,默认是Local System
  • 服务的名字: CoderWorker, 这里要注意和我们编写的服务里面的m_strSvcName服务名字要一致
  • 程序的位置: C:\Personal\WindowsServiceNormal.exe
  • 启动的方式: demand表示手动。而如果想做成开机启动可以设置为auto

启动服务

sc start CoderWorker 这个命令是不等待服务启动完毕的。如果想等待启动完毕后再返回可以调用net start CoderWorkder.

查询服务状态

sc query CoderWorker

停止服务

sc stop CoderWorker 这个命令是不等待服务启动完毕的。如果想等待启动完毕后再返回可以调用net stop CoderWorkder.

删除服务

sc delete CoderWorker,在调用这个命令之前,最好是先停止服务。

服务崩溃自动重启

这个功能是非常有用的一个功能。大多数的程序员都不敢保证自己写的程序永远都不会崩溃,尤其是C++程序员。那么当你编写的服务在客户的机器上运行时,如果崩溃后,程序就不再工作了,在有些情况下是不太能够接受的。Windows的服务框架提供了这个功能,只需要在Windows服务中进行配置即可,如下图:

参考

  1. MSDN: Service Control Manager
  2. MSDN: Writing a Service Main
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Windows服务控制管理器
  • 实现Windows服务
  • 服务的配置
    • 安装服务
      • 启动服务
        • 查询服务状态
          • 停止服务
            • 删除服务
              • 服务崩溃自动重启
              • 参考
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档