성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - .NET Core Console로 리눅스 daemon 프로그램 만드는 방법</h1> <p> 검색해 보면 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;' > Creating a Daemon with .NET Core (Part 1) ; <a target='tab' href='https://www.wintellect.com/creating-a-daemon-with-net-core-part-1/'>https://www.wintellect.com/creating-a-daemon-with-net-core-part-1/</a> Creating a Daemon with .NET Core (Part 2) ; <a target='tab' href='https://www.wintellect.com/creating-a-daemon-with-net-core-part-2/'>https://www.wintellect.com/creating-a-daemon-with-net-core-part-2/</a> </pre> <br /> 물론 저렇게 만들어도 되지만, 간단한 데몬을 만드는 경우라면 가능한 별다른 모듈에 대한 의존성 없이 단일 dll로 만드는 방법도 고려해 볼 수 있습니다. 시작은, 프로그램이 종료하지 못하도록 막고 있기만 하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset); ewh.WaitOne(); } </pre> <br /> 이렇게 만든 프로그램을 "/etc/systemd/system"에 등록하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 라즈베리 파이 - (윈도우의 NT 서비스처럼) 부팅 시 시작하는 프로그램 설정 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11374'>http://www.sysnet.pe.kr/2/0/11374</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // $ ls -l /usr/lib/systemd/system // $ ls -l /etc/systemd/system // $ systemctl list-unit-files $ cat /etc/systemd/system/dotnet-testd.service [Unit] Description=testd.dll running on Unix 3.10.0.957 [Service] WorkingDirectory=/home/tusr/testd/bin ExecStart=/usr/share/dotnet/dotnet /home/tusr/testd/bin/testd.dll KillSignal=SIGINT SyslogIdentifier=dotnet-testd [Install] WantedBy=multi-user.target </pre> <br /> 이후부터 systemctl 명령을 이용해 NT 서비스처럼 daemon으로 실행시킬 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [컴퓨터 시작 시 서비스가 로드하도록 등록] $ sudo systemctl enable dotnet-testd [서비스 지금 시작] $ sudo systemctl start dotnet-testd </pre> <br /> 그런데 종료가 문제입니다. 우선 systemctl의 종료 명령에는 2가지가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [service 파일에 등록한 KillSignal로 종료, 이번 예제에서는 SIGINT 발생] sudo systemctl stop dotnet-testd [프로세스 종료] sudo systemctl kill dotnet-testd </pre> <br /> 이 중에서 "kill" 명령어의 대응은 AppDomain.CurrentDomain.ProcessExit로 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AppDomain.CurrentDomain.ProcessExit += (s, e) => { CleanupResources(); WriteLog("Exited gracefully!"); }; </pre> <br /> 그런데, 이것만 하게 되면 systemctl stop 명령어에 대해서는 반응하지 않고 그냥 종료해 버립니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > sudo systemctl stop dotnet-testd (ProcessExit 없이 종료) sudo systemctl kill dotnet-testd ProcessExit 이벤트 발생 </pre> <br /> 따라서 systemctl stop에 대한 처리를 위해 Console.CancelKeyPress를 다음과 같이 추가할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // SIGINT에 반응 Console.CancelKeyPress += (s, e) => { WriteLog("stopped"); }; // 프로세스 종료에 반응 AppDomain.CurrentDomain.ProcessExit += (s, e) => { CleanupResources(); WriteLog("Exited gracefully!"); }; </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 systemctl stop dotnet-testd Jun 24 21:38:33 centos7 dotnet-testd: stopped sudo systemctl kill dotnet-testd Jun 24 21:48:49 centos7 dotnet-testd: Exited gracefully! </pre> <br /> 2가지 모두 반응했지만 재미있는 것은 SIGINT의 경우 ProcessExit 이벤트가 발생하지 않는다는 특이점이 있습니다. 만약 CancelKeyPress에서 ProcessExit로 흐르게 하고 싶다면 Cancel 속성을 true로 설정한 다음, EventWaitHandle을 시그널시켜서 프로세스를 종료하게 해 자연스럽게 ProcessExit가 발생하도록 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.CancelKeyPress += (s, e) => { WriteLog("stopped"); <span style='color: blue; font-weight: bold'>e.Cancel = true; ewh.Set();</span> }; AppDomain.CurrentDomain.ProcessExit += (s, e) => { CleanupResources(); WriteLog("Exited gracefully!"); }; </pre> <br /> 따라서 각각의 종료에 대해 로그는 다음과 같이 바뀝니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > sudo systemctl stop dotnet-testd Jun 24 21:41:57 centos7 dotnet-testd: stopped Jun 24 21:41:57 centos7 dotnet-testd: Exited gracefully! sudo systemctl kill dotnet-testd Jun 24 21:41:08 centos7 dotnet-testd: Exited gracefully! </pre> <br /> <hr style='width: 50%' /><br /> <br /> 부가적으로, 서비스 스스로 install/uninstall을 하도록 다음과 같은 식의 처리도 추가해 주면 좋을 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset); if (args.Length >= 1) { string netDllPath = typeof(Program).Assembly.Location; if (args[0] == "--install" || args[0] == "-i") { <span style='color: blue; font-weight: bold'>InstallService(netDllPath, true);</span> } else if (args[0] == "--uninstall" || args[0] == "-u") { <span style='color: blue; font-weight: bold'>InstallService(netDllPath, false);</span> } return; } // ...[생략]... ewh.WaitOne(); } 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 "; string dllFileName = Path.GetFileName(netDllPath); string osName = Environment.OSVersion.ToString(); FileInfo fi = null; try { fi = new FileInfo(netDllPath); } catch { } if (doInstall == true && fi != null && fi.Exists == false) { WriteLog("NOT FOUND: " + fi.FullName); return 1; } string serviceName = "dotnet-" + Path.GetFileNameWithoutExtension(dllFileName).ToLower(); string exeName = Process.GetCurrentProcess().MainModule.FileName; string workingDir = Path.GetDirectoryName(fi.FullName); string fullText = string.Format(serviceFile, dllFileName, osName, workingDir, exeName, fi.FullName, serviceName); string serviceFilePath = $"/etc/systemd/system/{serviceName}.service"; if (doInstall == true) { File.WriteAllText(serviceFilePath, fullText); WriteLog(serviceFilePath + " Created"); ControlService(serviceName, "enable"); ControlService(serviceName, "start"); } else { if (File.Exists(serviceFilePath) == true) { ControlService(serviceName, "stop"); File.Delete(serviceFilePath); WriteLog(serviceFilePath + " Deleted"); } } return 0; } </pre> <br /> 이 정도면 거의 틀을 갖췄군요. ^^ 이제 빌드하고 testd.dll과 testd.<a target='tab' href='http://www.sysnet.pe.kr/2/0/11953'>runtimeconfig.json</a> 파일만 리눅스 시스템에 복사하면 서비스로써 완벽하게 동작할 수 있습니다.<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 ./test.dll --install [서비스 해제] $ sudo dotnet ./test.dll --uninstall [서비스 시작] $ sudo systemctl start dotnet-testd [서비스 중지-1] $ sudo systemctl stop dotnet-testd [서비스 중지-2] $ sudo systemctl kill dotnet-testd </pre> <br /> 이 상태에서 여러분들의 업무 코드만 추가하면 됩니다. ^^ (그나저나 제가 리눅스에 잘 모르는 상태에서 만든 것이므로, 혹시 더 좋은 예제 코드가 있다면 덧글 부탁드립니다. ^^)<br /> <br /> (<a target='tab' href='https://github.com/stjeong/DotNetSamples/tree/master/NetCore/testd'>이 글의 예제 프로젝트 코드는 github</a>에 올려두었습니다.)<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1319
(왼쪽의 숫자를 입력해야 합니다.)