Dotnet创建Linux下的Service应用

创建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);
        }
    }
}

这个代码中,有两件事需要注意:

  1. 在前边Program.cs中加入UseSystemd()时,已经注入了IConfiguration。因此,可以在这个方法中直接引入并使用。换句话说,就是可以直接读取例如appsetting.json的内容;

  2. 是上边提到的,真正的服务响应在ExecuteAsync(CancellationToken stoppingToken)中。这儿没什么特别的,就是正常的写法。

上面这个,是服务端的程序,是响应。

下面我简单做个客户端的请求,供测试用。就不解释了,只列出步骤:

  1. 创建一个工程

% dotnet new console -o democlient
  1. 修改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

当然,这种方式只用于调试。正式运行时,还应该是上面的方式。

这就是今天的内容,希望能帮到大家。感觉有用的话,给个三连呗~

喜欢就来个三连,让更多人因你而受益

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值