创建Service应用,是一个服务端开发的必会技能。
前言
说到服务端应用,最常见的就是API服务。
除此之外,还有一类应用,比方一个Socket的服务器。这类型的应用,本身没有Web层,当然也不属于API服务。
通常大家会怎么做?
不讲究的做法,就是做一个Console应用,加载到后台一直跑着。
其实,还有另外一种做法,就是把应用加载到Services里,使应用以一个Service来做响应。这样可以依托操作系统的Services管理器来进行统一管理,自动运行和故障处理。
Dotnet做Window Service的内容,网上有很多。我今天写一个在Linux下做Service的方法。
创建Linux下的Service应用
创建一个LInux下的Service应用其实很简单,就分这么几步:
1. 用 Worker 模板创建工程
如果习惯用VS上创建,就找一下Worker Service
模板。
我是习惯从命令行创建,就一条命令:
% dotnet new worker -o projectname
Dotnet会自动造成工程,并自动引用Microsoft.Extensions.Hosting
包,因为这本身是一个Self-Hosting
应用。
2. 加入Linux Service扩展包
其实这就是一个包:Microsoft.Extensions.Hosting.Systemd
。这个包为应用提供了在Linux下使用Systemd守护进程的基础配置。
还是命令行:
% dotnet add package Microsoft.Extensions.Hosting.Systemd
3. 修改Program.cs
其实就是一行代码,把第二步引入的包加入应用。修改Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSystemd() // 加入的就是这一行。
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
到这儿,套路性的工作已经完成。简单吧?
我们来看一下现在的工程:
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Worker.cs
├── appsettings.Development.json
├── appsettings.json
└── workerdemo.csproj
大家会注意到,里面多了一个Worker.cs
的类文件。
看一下这个文件:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
这其实就是加载到Systemd里的服务的模板。我们需要的服务代码,需要加到ExecuteAsync(CancellationToken stoppingToken)
方法中。
我简单做个例子,在里面加入UDP服务,看代码:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IConfiguration _configuration;
public Worker(ILogger<Worker> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8000));
while (!stoppingToken.IsCancellationRequested)
{
UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
string message = Encoding.UTF8.GetString(udpReceiveResult.Buffer);
Console.WriteLine($"{udpReceiveResult.RemoteEndPoint.ToString()} - {message}");
await udpClient.SendAsync(Encoding.Default.GetBytes("Got"), 3, udpReceiveResult.RemoteEndPoint);
}
}
}
这个代码中,有两件事需要注意:
在前边Program.cs中加入
UseSystemd()
时,已经注入了IConfiguration
。因此,可以在这个方法中直接引入并使用。换句话说,就是可以直接读取例如appsetting.json
的内容;是上边提到的,真正的服务响应在
ExecuteAsync(CancellationToken stoppingToken)
中。这儿没什么特别的,就是正常的写法。
上面这个,是服务端的程序,是响应。
下面我简单做个客户端的请求,供测试用。就不解释了,只列出步骤:
创建一个工程
% dotnet new console -o democlient
修改Program.cs
static async Task Main(string[] args)
{
UdpClient udpClient = new UdpClient();
for (int i = 0; i < 10000; i++)
{
byte[] buffer = new byte[8 * 1024];
await Task.Run(() =>
{
udpClient.SendAsync(buffer, buffer.Length, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8000));
});
}
while (true)
{
UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
string message = Encoding.UTF8.GetString(udpReceiveResult.Buffer);
Console.WriteLine($"{udpReceiveResult.RemoteEndPoint.ToString()} - {message}");
}
Console.ReadKey();
}
运行一下,看看效果。
到这里,Service应用开发的工作已经完成。
下面是部署。
部署Service应用
Linux下面部署一个Service应用,只有两个步骤:
1. 创建Service定义
Linux下的每个Service,都会有个定义文件。这个文件存在于/etc/systemd/system
目录下。
下面我给出一个简单的Service模板:
[Unit]
Description=DemoProject
[Service]
Type=notify
ExecStart=dotnet /yourfolder/yourproject.dll
[Install]
WantedBy=multi-user.target
把这个内容保存为一个文件,例如叫demo.service
。然后把这个文件复制到/etc/systemd/system
下,并改为可执行。
简单说一下这个文件的一些项:
Description,是服务的名字。不重要,启动时,你用到的是文件名
demo.service
;Type,服务类型,使用Dotnet加载时,只能是这种类型。如果把程序编译为自包含程序,这个类型可以是simple;
ExecStart,启动程序的命令,是全路径的,要确保能找得到这个程序。上面例子中,
dotnet /yourfolder/yourproject.dll
,是因为dotnet命令是有PATH变量支持的。
这个文件的配置项有很多,包括定义是否需要自动重启、重启间隔等。如果需要,可以去这里查询。
2. 启动Service
有两种方法。
第一种是刷新Service守护
% systemctl daemon-reload
刷新守护时,守护进程会去/etc/systemd/system
目录下,寻找新加入的Service文件,并启动。
第二种是单独启动,有一系列命令:
启动
% systemctl start demo.service
停止
% systemctl stop demo.service
重启
% systemctl restart demo.service
查询状态
% systemctl status demo.service
嗯。这就是服务加载和停止了。
注意,这种方式加载的Service,是完全系统的服务,会没有任何输出。
如果需要调试,一种方式是加文件日志,另一种方式是用另一个命令启动:
% journalctl -u dnsserver.service
当然,这种方式只用于调试。正式运行时,还应该是上面的方式。
这就是今天的内容,希望能帮到大家。感觉有用的话,给个三连呗~
喜欢就来个三连,让更多人因你而受益