성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - Generic Host를 이용해 .NET 5로 리눅스 daemon 프로그램 만드는 방법</h1> <p> 지난번에는 별다른 의존성 없이 단순하게 만드는 방법을 소개했는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - .NET Core Console로 리눅스 daemon 프로그램 만드는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11958'>https://www.sysnet.pe.kr/2/0/11958</a> </pre> <br /> 이번에는 ASP.NET Core의 Generic host를 이용한 방법으로 작성해 보겠습니다. 이에 대해서는 마이크로소프트의 문서에 친절하게 설명하고 있으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Generic Host in ASP.NET Core ; <a target='tab' href='https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host'>https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host</a> </pre> <br /> 위의 내용을 보셔도 충분합니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 시작은 .NET 5 Console 프로그램으로 만듭니다. 그다음 Generic Host를 위한 관련 패키지를 추가하면 되는데요, 혹은 그냥 간단하게 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12496'>.NET Core 콘솔 프로젝트에서 Kestrel 호스팅 방법</a>" 글에서 설명한 것처럼 Console csproj 파일의 Sdk 항목을 "Microsoft.NET.Sdk.Web"으로 변경해도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Project Sdk="<span style='color: blue; font-weight: bold'>Microsoft.NET.Sdk.Web</span>"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> </Project> </pre> <br /> 이제 Program.cs의 Main 메서드를 다음과 같이 작성하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; namespace testd { class Program { static async Task Main(string[] args) { var host = new HostBuilder() .ConfigureServices((hostContext, services) => services.AddHostedService<<span style='color: blue; font-weight: bold'>ShutdownService</span>>()) .UseConsoleLifetime() .Build(); await host.RunAsync(); } } } </pre> <br /> 실질적인 서비스 코드는 별도의 ShutdownService.cs 파일을 만들어 다음과 같이 만들어 두면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Extensions.Hosting; using System.Threading; using System.Threading.Tasks; namespace testd { class ShutdownService : IHostedService { private bool pleaseStop; private Task BackgroundTask; private readonly IHostApplicationLifetime applicationLifetime; public ShutdownService(IHostApplicationLifetime applicationLifetime) { this.applicationLifetime = applicationLifetime; } public Task StartAsync(CancellationToken _) { Console.WriteLine("[testd] Starting service"); <span style='color: blue; font-weight: bold'>BackgroundTask = Task.Run(async () => { while (!pleaseStop) { await Task.Delay(50); } Console.WriteLine("[testd] Background task gracefully stopped"); });</span> return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("[testd] Stopping service"); pleaseStop = true; await BackgroundTask; Console.WriteLine("[testd] Service stopped"); } } } </pre> <br /> 당연히, BackgroundTask 속성에 할당한 Task.Run 코드에는 여러분이 원하는 코드를 넣어야 합니다. 사실상 위의 코드가 전부이고 나머지 팁/트릭은 "<a target='tab' href='https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host'>.NET Generic Host in ASP.NET Core</a>" 글의 내용을 참고해 멋을 좀 더 부리시면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 편의상, 서비스 등록/해제를 위한 코드를 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/11958'>C# - .NET Core Console로 리눅스 daemon 프로그램 만드는 방법</a>" 글에서 소개한 방법처럼 사용해도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; namespace testd { class Program { static async Task Main(string[] args) { <span style='color: blue; font-weight: bold'>if (args.Length >= 1) { string netDllPath = typeof(Program).Assembly.Location; if (args[0] == "--install" || args[0] == "-i") { InstallService(netDllPath, true); } else if (args[0] == "--uninstall" || args[0] == "-u") { InstallService(netDllPath, false); } return; }</span> var host = new HostBuilder() .ConfigureServices((hostContext, services) => services.AddHostedService<ShutdownService>()) .UseConsoleLifetime() .Build(); await host.RunAsync(); } static int InstallService(string netDllPath, bool doInstall) { // 2021-04-22 업데이트 // KillSignal=SIGINT 제거 // KillMode=mixed 추가 string serviceFile = @" [Unit] Description={0} running on {1} [Service] WorkingDirectory={2} ExecStart={3} {4} SyslogIdentifier={5} KillMode=mixed [Install] WantedBy=multi-user.target "; // ...[생략]... } static int ControlService(string serviceName, string mode) { // ...[생략]... } } } </pre> <br /> 이후 서비스 등록/해제 및 systemctl 관련 명령어를 다음과 같이 수행할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [서비스 등록] sudo dotnet ./testd.dll --install [서비스 해제] sudo dotnet ./testd.dll --uninstall [서비스 시작] sudo systemctl start dotnet-testd [서비스 중지-1 SIGINT] sudo systemctl stop dotnet-testd [서비스 중지-2 SIGTERM] sudo systemctl kill dotnet-testd // 기타 Ctrl + '\'키로 발생하는 SIGQUIT </pre> <br /> 확인을 위해 서비스 등록을 하고 "tail -F /var/log/syslog"로 보면 다음과 같은 로그가 찍혀 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Apr 21 22:27:48 testnix systemd[1]: Started testd.dll running on Unix 5.8.0.48. Apr 21 22:27:48 testnix dotnet-testd[758407]: [testd] Starting service </pre> <br /> 그리고 서비스 중지(systemctl kill)를 하면 이렇게 로그가 남고!<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Apr 21 22:28:30 testnix dotnet-testd[758407]: [testd] Stopping service Apr 21 22:28:30 testnix dotnet-testd[758407]: [testd] Background task gracefully stopped Apr 21 22:28:30 testnix dotnet-testd[758407]: [testd] Service stopped Apr 21 22:28:30 testnix systemd[1]: dotnet-testd.service: Succeeded. </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1748&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그나저나, 제가 이 글을 쓴 이유는 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12496'>.NET Core 콘솔 프로젝트에서 Kestrel 호스팅 방법</a>" 글에 달린 <a target='tab' href='https://www.sysnet.pe.kr/2/0/11958#14923'>덧글</a> 때문입니다. ^^;<br /> <br /> 현재 .NET 5로 해당 프로그램을 만들면 Ctrl + C에 대해 잘 반응을 하지만 daemon으로 등록해 두면 systemctl stop(SIGINT)에 대해 반응하지 않고 곧바로 프로세스가 종료됩니다. 종료가 된다는 것은 다행이지만 Console.CancelKeyPress 이벤트 핸들러가 실행되지 않으므로 아쉽게도 프로세스 종료 시 수행해야 할 특정 작업이 있다면 더 이상 실행이 되지 않습니다.<br /> <br /> 그래서 혹시나 마이크로소프트 측에서 만든 서비스 관련 코드라면 이에 대한 반응을 준비하지 않았나 싶어 Generic Host를 이용해 다시 한번 동일한 daemon 예제 코드를 작성해 본 것인데요, 마찬가지로 "systemctl stop"에는 반응하지 않았습니다. (즉,"Background task gracefully stopped" 등의 로그가 남지 않습니다.)<br /> <br /> 혹시 이 원인에 대해 리눅스 환경 및 닷넷 코어를 잘 아시는 분은 덧글 부탁드립니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 2021-04-22 업데이트: <a target='tab' href='#14927'>덧글에 주신 의견</a>에 따라, 서비스 등록 시 기존의 KillSignal=SIGINT를 제외하고 KillMode=mixed를 추가하면 systemctl stop/kill에 대해 SIGTERM 신호를 받을 수 있어 ProcessExit 이벤트가 실행됩니다. (당분간이라고 해야 할지 모르겠지만) 일단은 이런 방법으로 stop/kill에 대해 수행할 코드가 있다면 대처하시면 되겠습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1206
(왼쪽의 숫자를 입력해야 합니다.)