写程序,难免会遇到须要做成零碎服务的需要。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 等)
- ……
既然做服务不难,那就不要太纠结如何“做”(提供)服务,还是多纠结纠结如何做好(设计)服务吧。