.NET Core + C#でLinuxのDeamon作れないかなと思ったら、思ったより簡単に作れるようになっていたので覚え書き。Program.csとDaemonService.csの中味はほぼ定型なのでサンプルからそのまま流用します。
以下のサンプルは.NET Core 3.1で記述しています。
// Program.cs using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DaemonSample { class Program { public static async Task Main(string[] args) { var builder = new HostBuilder() .ConfigureAppConfiguration((hostingContext, config) => { config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureServices((hostContext, services) => { services.AddOptions(); services.Configure<DaemonConfig>(hostContext.Configuration.GetSection("Daemon")); services.AddSingleton<IHostedService, DaemonService>(); }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); }); await builder.RunConsoleAsync(); } } }
// DaemonService.cs using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace DaemonSample { class DaemonService : IHostedService, IDisposable { private readonly ILogger _logger; private readonly IOptions<DaemonConfig> _config; public DaemonService(ILogger<DaemonService> logger, IOptions<DaemonConfig> config) { _logger = logger; _config = config; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting daemon: " + _config.Value.DaemonName); _logger.LogInformation("arg1:{0}", _config.Value.arg1); _logger.LogInformation("arg2:{0}", _config.Value.arg2); // ここでサーバー本体の処理をTaskとして起動します。 return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Stopping daemon."); return Task.CompletedTask; } public void Dispose() { _logger.LogInformation("Disposing...."); } } }
// DaemonConfig.cs using System; using System.Collections.Generic; using System.Text; namespace DaemonSample { class DaemonConfig { public string DaemonName { get; set; } public string arg1{ get; set; } public string arg2{ get; set; } } }
起動時のコマンドラインは下記の通り。コマンドライン引数の受け渡し方が非常に独特です。
/usr/bin/dotnet run --project deamonsample --configuration Release --Daemon:DaemonName="Sample Daemon" Daemon:arg1="argument 1" Daemon:arg2="argument 2"
Demonとして起動するにはsystemdを使用します。/etc/systemd/systemにdeamonsample.serviceファイルを作成し、下記の様に記述します。
[Unit] Description=deamon sample After=network.target [Service] ExecStart=/usr/bin/dotnet run --project atozipsrv --configuration Release --Daemon:DaemonName="deamonsample " Daemon:arg1="argument 1" Daemon:arg2="argument 2" WorkingDirectory=/usr/src/deamonsample Restart=always StandardOutput=syslog StandardError=syslog SyslogIdentifier=atozip Environment=DOTNET_CLI_HOME=/var/src/deamonsample [Install] WantedBy=multi-user.target
後は通常のsystemdのDeamon同様に以下のコマンドで起動します。
$ systemctl start deamonsample