windows服务编程

news2024/11/24 18:31:21

文章目录

    • 前言
      • 方案一:服务程序
      • 方案二:后台程序
      • 对比
    • windows服务编程
    • windows服务控制
    • 附录 - 完整代码

前言

在linux中,如果需要一个程序在后台持续提供服务,我们一般会使用守护进程。

守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。它们没有控制终端,在后台运行。

在windows上,如何使用cpp创建一个守护进程呢?这里给出两种方案。其一是服务程序 - Win32 apps | Microsoft Learn。其二是,创建一个后台程序,使用信号控制开启与关闭,并将程序加入开机自启。服务程序的方案是比较好的。

下面,我们介绍下这两个方案。

注:本文完整代码见仓库。


方案一:服务程序

如何检查Windows系统下某服务的状态是启用还是禁用

"win+R"键打开运行->输入输入“services.msc”按回车。

下面这个“test_service"是本文创建的服务,后面会介绍如何实现。

在这里插入图片描述


方案二:后台程序

我们需要先编写这样一个程序:程序可以后台运行;程序可以接收信号,以进行控制,如关闭;

此时,我们将程序加入开机自启,即可实现和上面服务程序类似的功能。

至于如何将程序加入开机自启,可参考:linux-windows服务设置-##windows中程序开机自启


对比

方案二,相对于方案一,缺点非常明显,需要自己写启动/停止等代码。或者说,方案二没有接入windows系统的服务管理。

方案二的缺点,某时候也是优点。如果有一套跨平台的信号库,如boost asio signal_set - 1.81.0,那么一套相同的代码,不需要封装即可写出跨平台的服务程序。当然,前提是,不在意windows的服务管理(windows service control manager, SCM)程序。


windows服务编程

下面介绍下windows服务编程的API。

参考:

  • 服务程序 - Win32 apps | Microsoft Learn

  • 实现一个Windows服务_一个程序员的修炼之路的博客-CSDN博客_如何写一个windows服务

上面两个链接看明白,基本不用看下面的内容

下面仅仅是我的学习记录吧。

第一步:服务入口点 - Win32 apps | Microsoft Learn。

  • SCM等待调用StartServiceCtrlDispatcher。

    • 如果 StartServiceCtrlDispatcher 成功(返回值为非零值),则调用线程不会返回,直到进程中的所有正在运行的服务都进入SERVICE_STOPPED状态。 该线程充当控制调度程序。SCM 通过命名管道向此线程发送控制请求。

    • 如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。

SERVICE_STATUS_HANDLE service_status_handle;
SERVICE_STATUS service_statu;
LPSTR service_name = "test_service";

void start() {
    SERVICE_TABLE_ENTRYA service_table[] = {
        {service_name, (LPSERVICE_MAIN_FUNCTIONA)service_main},
        {NULL, NULL}
    };
    WriteLog("enter main.");

    if(StartServiceCtrlDispatcher(service_table) ==0) {
        WriteLog("StartServiceCtrlDispatcher fail.");
        return;
    }
}

第二步:ServiceMain 函数 - Win32 apps | Microsoft Learn。

  • 当服务控制程序请求新服务运行时,服务控制管理器 (SCM) 启动服务并向控制调度程序发送启动请求。 控制调度程序创建一个新线程,用于为服务执行 ServiceMain 函数。

  • ServiceMain 函数中需要注册控制处理函数。

  • 执行初始化。如果初始化时间应超过一秒,则服务应使用以下初始化技术之一:

    • 调用 SetServiceStatus 函数以报告SERVICE_RUNNING,但在初始化完成之前不接受任何控件。 该服务通过将 dwCurrentState 设置为 SERVICE_RUNNING,并在 SERVICE_STATUS 结构中将dwControlsAccepted 设置为 0 来执行此操作。 这可确保 SCM 在服务准备就绪之前不会向服务发送任何控制请求,并释放 SCM 来管理其他服务。 对于性能,建议使用此方法进行初始化,尤其是对于自动启动服务。

    • 报告SERVICE_START_PENDING,不接受任何控件,并指定等待提示。 如果服务的初始化代码执行所需时间超过初始等待提示值的任务,则代码必须定期调用 SetServiceStatus 函数, (可能带有修订的等待提示) 来指示正在进行进度。使用此方法的服务还可以指定检查点值,并在长时间初始化期间定期递增值。

    • 初始化完成后,调用 SetServiceStatus 将服务状态设置为SERVICE_RUNNING并指定服务准备接受的控件。 有关控件列表,请参阅 SERVICE_STATUS 结构。

    • 如果服务正在初始化或运行时发生错误,则服务应调用 SetServiceStatus 以将服务状态设置为SERVICE_STOP_PENDING(如果清理时间较长)。 清理完成后,调用 SetServiceStatus 将服务状态设置为从最后一个线程终止SERVICE_STOPPED。 请务必设置SERVICE_STATUS结构的 dwServiceSpecificExitCode 和 dwWin32ExitCode 成员来标识错误。

  • 初始化成功后,执行服务任务。服务状态的任何更改都要求调用 SetServiceStatus 来报告新的状态信息。(是的,我们的业务代码在这里)

void service_main(DWORD argc, LPSTR *argv) {
    // 注册控制函数
    service_status_handle = RegisterServiceCtrlHandler(service_name, service_control);
    if(service_status_handle == 0) {
        WriteLog("RegisterServiceCtrlHandler fail.");
        return;
    }

    // 告诉SCM,当前服务正在启动中
    // dwWaitHint=3000ms是预估的时间. 
    // 如果超过这个时间,且dwCheckPoint 尚未递增或 dwCurrentState 未更改,则服务控制管理器或服务控制程序可以假定出错并且应停止服务
    if(ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000) == 0) {
        WriteLog("start fail");
        ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);
        WriteLog(std::to_string(GetLastError()).c_str());
        return;
    }

    // 准备工作

    // 做完准备工作后,告诉SCM服务已经启动
    if(ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0) ==0) {
        WriteLog("run fail");
        ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);
        return;
    }

    // 运行正在工作代码,通常是个无限循环
    WriteLog("work.run()");
    work::instance().run();
    return;
}

第三步,实现服务控制处理程序函数 - Win32 apps | Microsoft Learn

每个服务都有一个控制处理程序, 即处理程序 函数,当服务进程从服务控制程序收到控件请求时,由控制调度程序调用。 因此,此函数在控件调度程序上下文中执行。

  • 服务调用 RegisterServiceCtrlHandler 或 RegisterServiceCtrlHandlerEx 函数来注册其服务控制处理程序函数。

  • 调用服务控制处理程序时,服务必须调用 SetServiceStatus 函数,以便仅在处理控制代码导致服务状态发生更改时才将其状态报告给 SCM。 如果处理控制代码不会导致服务状态更改,则无需调用 SetServiceStatus。

  • 如果服务接受 SERVICE_CONTROL_STO 控制代码,则必须在收到后停止,转到 SERVICE_STOP_PENDING 或 SERVICE_STOPPED 状态。控制处理程序必须在 30 秒内返回,或者 SCM 返回错误。 如果服务在执行控制处理程序时必须执行较长处理,则应创建一个辅助线程来执行冗长的处理,然后从控制处理程序返回。

void service_control(DWORD ctrl_code) {
    switch(ctrl_code) {
        case SERVICE_CONTROL_SHUTDOWN: // 系统关闭
        case SERVICE_CONTROL_STOP: // 服务停止
            // call stop funciton
            ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 3000);
            WriteLog("work.stop()");
            work::instance().stop();
            ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 3000);
            break;
        default:
            break;
    }
}

过程中使用的服务状态转换 - Win32 apps | Microsoft Learn代码如下。

BOOL ReportSvcStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode, DWORD dwWaitHint) {
    static DWORD dwCheckPoint = 1;
    // Fill in the SERVICE_STATUS structure.

    // 官方提供的示例中没有指定服务:https://learn.microsoft.com/zh-cn/windows/win32/services/sample-mc
    // 开始的时候,我没有下面这一行。我用sc.exe type=own指定,但是SetServiceStatus报错为ERROR_INVALID_DATA
    // 不知道是不是代码中必须要指定,否则会报错
    service_statu.dwServiceType = SERVICE_WIN32_OWN_PROCESS;

    service_statu.dwCurrentState = dwCurrentState;
    service_statu.dwWin32ExitCode = dwWin32ExitCode;
    service_statu.dwWaitHint = dwWaitHint; // 挂起开始、停止、暂停或继续操作所需的估计时间

    if (dwCurrentState == SERVICE_START_PENDING) {
        service_statu.dwControlsAccepted = 0; // 正在启动,此时不接受控制码
    } else {
        service_statu.dwControlsAccepted = SERVICE_ACCEPT_STOP; // 服务可以停止。此控制代码允许服务接收 SERVICE_CONTROL_STOP 通知
    }

    // 服务定期递增的检查点值,以在长时间的启动、停止、暂停或继续操作期间报告其进度
    if ( (dwCurrentState == SERVICE_RUNNING) ||
        (dwCurrentState == SERVICE_STOPPED) )
        service_statu.dwCheckPoint = 0;
    else service_statu.dwCheckPoint = dwCheckPoint++;

    // Report the status of the service to the SCM.
    return SetServiceStatus(service_status_handle, &service::service_statu);
he SCM.
    return SetServiceStatus(service_status_handle, &service::service_statu);
}

如果明白上面过程,看下能不能理解这张图。图片来自:实现一个Windows服务_一个程序员的修炼之路的博客-CSDN博客_如何写一个windows服务

在这里插入图片描述


windows服务控制

上面我在代码中并没有去实现服务安装的代码。因为sc.exe程序可以帮助我们做到。命令的使用,可以参考下面两个链接:

  • Sc | Microsoft Learn

  • sc.exe配置 | Microsoft Learn

下面,我们使用命令,控制上面的服务。

# 安装
 sc.exe create test_service binpath=xxx\test_service.exe  start=demand error=normal
 
# 启动
 sc.exe start test_service
 
# 查询
PS C:\Windows\system32> sc.exe query test_service

SERVICE_NAME: test_service
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
 
# 停止
sc.exe stop test_service
 
# 删除 
sc.exe delete test_service

附录 - 完整代码


#include "service.hpp"

int main(int argc, char* argv[]) {
    service::start();
    WriteLog("process end");
    return 0;
}

#include "work.hpp"
#include <windows.h>
#include <winsvc.h>
#include <iostream>
#include <string>

namespace service {

void start();
void service_main(DWORD argc, LPSTR *argv);
void service_control(DWORD ctrl_code);
BOOL ReportSvcStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode, DWORD dwWaitHint);

SERVICE_STATUS_HANDLE service_status_handle;
SERVICE_STATUS service_statu;
LPSTR service_name = "test_service";

void start() {
    SERVICE_TABLE_ENTRYA service_table[] = {
        {service_name, (LPSERVICE_MAIN_FUNCTIONA)service_main},
        {NULL, NULL}
    };
    WriteLog("enter main.");

    if(StartServiceCtrlDispatcher(service_table) ==0) {
        WriteLog("StartServiceCtrlDispatcher fail.");
        return;
    }
}

void service_main(DWORD argc, LPSTR *argv) {
    // 注册控制函数
    service_status_handle = RegisterServiceCtrlHandler(service_name, service_control);
    if(service_status_handle == 0) {
        WriteLog("RegisterServiceCtrlHandler fail.");
        return;
    }

    // 告诉SCM,当前服务正在启动中
    // dwWaitHint=3000ms是预估的时间. 
    // 如果超过这个时间,且dwCheckPoint 尚未递增或 dwCurrentState 未更改,则服务控制管理器或服务控制程序可以假定出错并且应停止服务
    if(ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000) == 0) {
        WriteLog("start fail");
        ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);
        WriteLog(std::to_string(GetLastError()).c_str());
        return;
    }

    // 准备工作

    // 做完准备工作后,告诉SCM服务已经启动
    if(ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0) ==0) {
        WriteLog("run fail");
        ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);
        return;
    }

    // 运行正在工作代码,通常是个无限循环
    WriteLog("work.run()");
    work::instance().run();
    return;
}

void service_control(DWORD ctrl_code) {
    switch(ctrl_code) {
        case SERVICE_CONTROL_SHUTDOWN: // 系统关闭
        case SERVICE_CONTROL_STOP: // 服务停止
            // call stop funciton
            ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 3000);
            WriteLog("work.stop()");
            work::instance().stop();
            ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 3000);
            break;
        default:
            break;
    }
}

BOOL ReportSvcStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode, DWORD dwWaitHint) {
    static DWORD dwCheckPoint = 1;
    // Fill in the SERVICE_STATUS structure.

    // 官方提供的示例中没有指定服务:https://learn.microsoft.com/zh-cn/windows/win32/services/sample-mc
    // 开始的时候,我没有下面这一行。我用sc.exe type=own指定,但是SetServiceStatus报错为ERROR_INVALID_DATA
    // 不知道是不是代码中必须要指定,否则会报错
    service_statu.dwServiceType = SERVICE_WIN32_OWN_PROCESS;

    service_statu.dwCurrentState = dwCurrentState;
    service_statu.dwWin32ExitCode = dwWin32ExitCode;
    service_statu.dwWaitHint = dwWaitHint; // 挂起开始、停止、暂停或继续操作所需的估计时间

    if (dwCurrentState == SERVICE_START_PENDING) {
        service_statu.dwControlsAccepted = 0; // 正在启动,此时不接受控制码
    } else {
        service_statu.dwControlsAccepted = SERVICE_ACCEPT_STOP; // 服务可以停止。此控制代码允许服务接收 SERVICE_CONTROL_STOP 通知
    }

    // 服务定期递增的检查点值,以在长时间的启动、停止、暂停或继续操作期间报告其进度
    if ( (dwCurrentState == SERVICE_RUNNING) ||
        (dwCurrentState == SERVICE_STOPPED) )
        service_statu.dwCheckPoint = 0;
    else service_statu.dwCheckPoint = dwCheckPoint++;

    // Report the status of the service to the SCM.
    return SetServiceStatus(service_status_handle, &service::service_statu);
}
}; // namespace service 

#include "utils.hpp"
#include <windows.h>

class work {
private:
    HANDLE m_work_event_handle;
public:
    static work &instance() {
        static work inst;
        return inst;
    }
    void run() {
        m_work_event_handle = CreateEvent(NULL, TRUE, FALSE, NULL);
        WaitForSingleObject(m_work_event_handle, INFINITE); // 无限等待,用于模拟一个服务不断运行的功能
        WriteLog("wait end\n");
    }
    void stop() {
        SetEvent(m_work_event_handle);
    }    
};

#include <fstream>
// 通常,不要将这样的函数放在hpp文件中
void WriteLog(const char* str)
{
    std::ofstream outfile;
    outfile.open("E:\\ServiceOutFile.txt", std::ios::out|std::ios::app);
    outfile << str << std::endl;
    return;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Canonical为所有支持的Ubuntu LTS系统发布了新的Linux内核更新

导读Canonical近日为所有支持的Ubuntu LTS系统发布了新的Linux内核更新&#xff0c;以解决总共19个安全漏洞。新的Ubuntu内核更新仅适用于长期支持的Ubuntu系统&#xff0c;包括Ubuntu 22.04 LTS&#xff08;Jammy Jellyfish&#xff09;、Ubuntu 20.04 LTS&#xff08;Focal F…

需求:节目上传至MINIO后,使用mqtt进行上报

需求&#xff1a;节目上传至MINIO后&#xff0c;使用mqtt进行上报 环境准备 文件管理平台&#xff1a;首先需要使用minio搭建属于自己的对象存储&#xff08;此步骤跳过&#xff09; 通信方式&#xff1a;MQTT方式&#xff0c;客户端测试工具&#xff1a;MQTTX&#xff08;ht…

AI - stable-diffusion(AI绘画)的搭建与使用

最近 AI 火的一塌糊涂&#xff0c;除了 ChatGPT 以外&#xff0c;AI 绘画领域也有很大的进步&#xff0c;以下几张图片都是 AI 绘制的&#xff0c;你能看出来么&#xff1f; 一、环境搭建 上面的效果图其实是使用了开源的 AI 绘画项目 stable-diffusion 绘制的&#xff0c;这是…

《MySQL学习》 表中随机取记录的方式

一.初始化测试表 创建表 words CREATE TABLE words ( id int(11) NOT NULL AUTO_INCREMENT, word varchar(64) DEFAULT NULL, PRIMARY KEY (id)) ENGINEInnoDB;插入测试数据 create procedure idata()begin declare i int; set i 0; while i<10000 do insert into words…

【计算机网络】TCP的可靠性传输机制和常见配置讲解

文章目录1.TCP的可靠性传输机制2.TCP的传输优化机制 Nagle算法和延迟确认3.Linux服务器常见网络内核参数配置4. Linux服务器生产环境常见问题1.TCP的可靠性传输机制 TCP的可靠性传输机制 ACK机制 接收方收到TCP 数据包&#xff0c;要响应一个确认消息 acknowledgement&#xff…

Jinja2----------模板渲染、模板访问对象属性

目录 1.Jinja2 1.简介 2.Jinja2模板 2.模板渲染 app.py templates/index.html templates/blog_detail.html 效果 3.模板访问对象属性 app.py templates/index.html 效果 1.Jinja2 1.简介 Jinja2是Python下一个被广泛应用的模版引擎&#xff0c;他的设计思想来…

k8s-Pod基础

文章目录一、资源限制二、Pod 的两种使用方式三、Pod 资源共享四、底层容器Pause1、Pause共享资源1.1 网络1.2 存储1.3 小结2、Pause主要功能3、Pod 与 Pause 结构的设计初衷五、Pod容器的分类1、基础容器&#xff08;infrastructure container&#xff09;2、初始化容器&#…

行测-判断推理-图形推理-位置规律-平移

位置平移&#xff0c;选D空白每次顺时针移动一格&#xff0c;黑色圆每次逆时针移动2格选C两个黑色⚪&#xff0c;每次顺时针移动2格白色⚪&#xff0c;先到对角位置&#xff0c;再顺时针移动一格选B三角形的底&#xff0c;顺时针移动三角形的顶点&#xff0c;在正方形的内部顺时…

大数据周会-本周学习内容总结03

目录 01【大数据导论与Linux基础】 02【Apache Hadoop、HDFS】 03【Hadoop MapReduce与Hadoop YARN】 04【数据仓库基础与Apache Hive入门】 05【Apache Hive DML语句与函数使用】 06【Hadoop生态综合案例&#xff1a;陌陌聊天数据分析】 01【大数据导论与Linux基础】 大…

如何从0创建Spring Cloud Alibaba(多模块)

以一个父工程带两个Module&#xff08;test1、test2&#xff09;为例。 一、创建父工程 由于是模块化项目&#xff0c;那么父工程不需要实际的代码逻辑&#xff0c;因此无需创建src&#xff0c;那么可以有几种方式创建&#xff0c;例如&#xff1a; 使用Spring Initializr脚…

【跟着ChatGPT学深度学习】ChatGPT带我入门NLP

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Unity Jobsystem ECS

简介随着ECS的加入&#xff0c;Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来&#xff0c;我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS&#xff0c;也许不少人需要些时间适应ECS的使用&#xff0c;…

学python的第二天---差分

一、改变数组元素&#xff08;差分&#xff09;方法一&#xff1a;差分数组map(int,input().split())for b in arr[:n]:print(1 if b else 0,end )方法二&#xff1a;区间合并interval.sort(keylambda x:x[0])二、差分a [0] list(map(int, input().split())) a[n 1:]三、差…

Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新

0.相关分享&#xff1a; Android从屏幕刷新到View的绘制&#xff08;一&#xff09;之 Window、WindowManager和WindowManagerService之间的关系 Android从屏幕刷新到View的绘制&#xff08;二&#xff09;之Choreographer、Vsync与屏幕刷新 1. 相关类 Choreographer 编舞者…

MySQL创建表

在创建表时需要提前了解mysql里面的数据类型 常见的数据类型 创建表方式1&#xff1a; 格式&#xff1a; CREATE TABLE [IF NOT EXISTS] 表名( 字段1, 数据类型 [约束条件] [默认值], 字段2, 数据类型 [约束条件] [默认值], 字段3, 数据类型 [约束条件] [默认值], …… [表约束…

英语基础语法学习(B站英语电力公司)

1. 句子结构 五大基本句型&#xff1a; 主谓主谓宾主谓宾宾主谓宾宾补主系表 谓语&#xff1a; 一般来说&#xff0c;谓语是指主语发出的动作。&#xff08;动词&#xff09;但是很多句子是没有动作的&#xff0c;但是还是必须要有谓语。&#xff08;此时需要be动词&#x…

echo命令

这是一条内置命令。 输出指定的字符串 一、语法 echo [选项] [参数] 二、选项 -e&#xff1a;激活转义字符。 使用-e选项时&#xff0c;若字符串中出现以下字符&#xff0c;则特别加以处理&#xff0c;而不会将它当成一般文字输出&#xff1a; \a 发出警告声&#xff1b; \b 删…

k8s-yaml文件

文章目录一、K8S支持的文件格式1、yaml和json的主要区别2、YAML语言格式二、YAML1、查看 API 资源版本标签2、编写资源配置清单2.1 编写 nginx-test.yaml 资源配置清单2.2 创建资源对象2.3 查看创建的pod资源3、创建service服务对外提供访问并测试3.1 编写nginx-svc-test.yaml文…

pytorch入门2--数据预处理、线性代数的矩阵实现、求导

数据预处理是指将原始数据读取进来使得能用机器学习的方法进行处理。 首先介绍csv文件&#xff1a; CSV 代表逗号分隔值&#xff08;comma-separated values&#xff09;&#xff0c;CSV 文件就是使用逗号分隔数据的文本文件。 一个 CSV 文件包含一行或多行数据&#xff0c;每一…

尚硅谷nginx基础

nginx1. nginx安装1.1版本区别1.2安装步骤1.3 启动nginx1.4关于防火墙1.5 安装成系统服务1.6 配置nginx环境变量2. nginx基本使用2.1 基本运行原理2.2 nginx配置文件2.2.1 最小配置2.2.1.1 基本配置说明2.3 虚拟主机2.3.1域名、dns、ip地址的关系2.3.2IP地址和DNS地址的区别2.3…