乐趣区

关于服务:把程序做成系统服务

写程序,难免会遇到须要做成零碎服务的需要。Windows 下写零碎服务须要实现一些特定的接口,做起来有肯定难度,所以不少程序采纳了 近似的备选计划 —— 做成带零碎任务栏图标的桌面利用。然而,服务之所以是服务,就在于他有一个十分重要的特点:能够开机自启动,而且不须要用户登录。要不然每次重启还得人工去登录,是件如许辛苦的事件。Windows 当然是能够设置主动登录的,但如果是托管服务器,你真释怀主动登录吗?

而 Linux 上面仿佛就要不便得多,大略不须要 GUI 继续运行的程序都能够做成服务。

1. 在 Windows 中做服务

先说 Windows。如果你还在用 Windows XP,那咱们就此别过 ……

1.1. Windows Service Wrapper

[Windows Service Wrapper] 是全称。其简称 WinSW 的知名度可能更高一些。

WinSW 基于 .NET Framework 4.6.1 和 .NET 5 实现,所以至多须要 Windows 7 SP1 / Windows Server 2008 R2 SP1 才能够应用。它能够把任意 Windows 程序封装成 Windows 服务,你所须要做的,只是写个配置文件,而后用 WinSW 注册一个 Windows 服务即可。WinSW 下载下来是个独立的可执行文件,应用前须要写一个与可执行文件名同名但扩展名是 .xml 的配置文件置于同一目录下。

举例来说,Nginx 自身并没有提供注册成 Windows 服务的能力,如果须要注册成 Windows 服务,就能够用 WinSW 来封装一下。把下载的 WinSW 可执行文件改名为 winsw.exe(轻易改成什么名字都行,配置文件名按雷同的名称创立即可),放在 nginx 的主目录上面,创立配置文件之后的目录构造大略是这样:

[-] nginx
 |-- conf
 |-- ...(其余 nginx 的目录或文件)
 |-- nginx.exe
 |-- winsw.exe
 `-- winsw.xml

winsw.xml 中的配置内容如下,看正文就能了解。

<service>
    <!-- 配置服务名称 nginx-service,显示名称 Nginx Service,以及服务形容 -->
    <id>nginx-service</id>
    <name>Nginx Service</name>
    <description>Nginx Service</description>

    <!-- 服务运行的工作目录,给绝对路径 -->
    <workingdirectory>C:\Local\Nginx</workingdirectory>

    <!-- 服务可执行文件,给绝对路径 -->
    <executable>C:\Local\Nginx\nginx.exe</executable>

    <!-- 进行服务的可执行文件 -->
    <stopexecutable>C:\Local\Nginx\nginx.exe</stopexecutable>
    <!-- 进行服务的参数 -->
    <stoparguments>-s stop</stoparguments>

    <priority>Normal</priority>
    <stoptimeout>15 sec</stoptimeout>
    <stopparentprocessfirst>false</stopparentprocessfirst>

    <!-- 配置服务类型是「主动」启动 -->
    <startmode>Automatic</startmode>
    <waithint>15 sec</waithint>
    <sleeptime>1 sec</sleeptime>

    <!-- 将服务的控制台输入(规范输入 / 谬误输入)写入日志 -->
    <!-- 其中 %BASE% 是指 winsw.exe 所在目录 -->
    <!-- 参考:https://github.com/winsw/winsw/blob/master/doc/loggingAndErrorReporting.md -->
    <logpath>%BASE%\logs</logpath>
    <log mode="roll-by-time">
        <pattern>yyyyMMdd</pattern>
    </log>
</service>

这个配置创立了名为 nginx-service 的 Windows 服务,它在 Windows 的「服务 (services.msc)」显示名称为 Nginx Service。启动服务的时候间接运行 nginx.exe 来启动,这是一个会执行占用控制台的程序;而进行服务则是运行 nginx.exe -s stop,可执行程序和参数别离配置在 <stopexecutable><stoparguments> 中 —— 由此不难推断,如果启动服务须要参数,是配置在 <arguments> 中的。

具体的配置能够在 github 库里的 XML configuratoin file 中查到,也能够查到一些示例。

配置实现之后运行 winsw.exe install 即可装置为 Windows 服务。装置实现之后能够应用 winsw.exe start 命令启动服务,也能够去 Windows 的服务管理器启动,或者应用 net start 命令来启动。github 库首页的 Usage 局部有残缺的命令阐明。

1.2. 用 .NET Framework/Core/5 本人写一个

用 .NET 写个服务还是比拟容易的,因为有现成的包(组件)能够用:NuGet Gallery | Microsoft.Extensions.Hosting.WindowsServices,官网出品。它至多须要依赖两个包:

  • NuGet Gallery | Microsoft.Extensions.Hosting
  • NuGet Gallery | Microsoft.Extensions.Hosting.Abstractions

在引入组件之后,只须要大量代码就能够让以后 .NET 的 Console Application 成为一个反对 Windows 服务接口的服务程序。

// Program.cs

class Program {static async Task<int> Main(string[] args) {await CreateHostBuilder(args).Build().RunAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) {return Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) => {services.AddHostedService<DaemonService>();
            })
            .UseWindowsService();}
}

留神到 AddHostedService<DaemonServce>,这里的 DaemonServce 是一个本人实现的服务业务类,命名自在,但须要从 Microsoft.Extensions.Hosting.BackgroundService 继承

class DaemonService : BackgroundService {protected override async Task ExecuteAsync(CancellationToken stoppingToken) {// TODO 提供服务内容的代码}
}

服务的业务代码通常都是继续运行,或者监听的代码。如果是计划性 / 周期性的工作,能够思考应用 Quartz 来实现。

程序实现后能够应用 Windows 提供的 sc 命令来注册 / 登记服务。假如生成的程序是 MyService.exe,那么注册、配置和启动服务的命令如下:

sc create "my-service" binPath="C:\MyService\MyService.exe --service"
sc config "my-service" start= auto
sc start "my-service"

留神:binPath 中应该给绝对路径。

2. 在 Ubuntu 中做 Systemd 服务

Linux 下服务品种比拟多,最近次要是用 Ubuntu,所以做 Ubuntu 下的 Systemd 服务。

假如咱们写了一个 .NET 5 的 ASP.NET 利用,放在 /app/my-web/,主文件是 MyWeb.dll。如果用命令行启动这个 Web 应该应该是

cd /app/my-web
dotnet MyWeb.dll

留神:须要提前准备好 .NET 5 的运行环境,可参考在 Ubuntu 上装置 .NET – .NET | Microsoft Docs。

接下来是写 Systemd 服务配置。配置文件名起为 my-web.service,放在 /etc/systemd/system 目录下。内容(含正文)如下:

[Unit]
# 服务阐明
Description=My Web Application
# 在启动网络服务之后启动
After=network.target

[Service]
# 总是重启(无论什么起因完结都会立刻重启)Restart=always
RestartSec=10
# 工作目录
WorkingDirectory=/app/my-web
# 启动服务的命令
ExecStart=/usr/bin/dotnet MyWeb.dll
# 通过杀主过程来完结服务
ExecStop=/bin/kill -HUP $MAINPID
TimeoutStopSec=5
KillMode=mixed
SyslogIdentifier=my-web
# 指定运行此服务的用户,波及到目录拜访权限等问题
User=james

[Install]
WantedBy=multi-user.target

配置完之后还不能马上启动服务,须要 systemd 从新加载配置,而后才启动服务:

sudo systemctl daemon-reload
sudo systemctl start my-web

顺便,再介绍一下,如果想在内容公布之后主动重启,须要加两个配置文件,一个 .path 监控变动,一个 .service 来重启 my-web

  • restart-my-web.path
[Path]
# 监控主文件 MyWeb.dll 的变动,如果有变动会触发 restart-my-web.service 启动
PathModified=/app/my-web/MyWeb.dll

[Install]
WantedBy=multi-user.target
  • restart-my-web.service
[Unit]
Description=My Web Restarter
After=network.target

[Service]
Type=oneshot
# 防抖,60 秒内只启动 1 次
ExecStartPre=/bin/sleep 60
# 重启 my-web.service
ExecStart=/bin/systemctl restart my-web.service

[Install]
WantedBy=multi-user.target

3. 小小的总结一下

做服务并不难,下面惟一的一个须要写代码的形式,还是开箱即用的组件实现的。但话说回来,做服务不难,做服务的设计还是有不少事件须要思考。比方

  • 如何监控服务的状态?—— 过程监控、心跳查看……
  • 如何剖析服务中呈现的谬误?—— 系统日志
  • 如何提供 GUI 来对服务进行治理?—— Web 或其余 UI 跟服务过程进行交互(过程通信、治理 API 等)
  • ……

既然做服务不难,那就不要太纠结如何“做”(提供)服务,还是多纠结纠结如何做好(设计)服务吧。

退出移动版