지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]
[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]
[강좌로 보는 서비스 프로그래밍] 가끔 쓸모있는 관련 함수들 [4/?]
[강좌로 보는 서비스 프로그래밍] 시작/중지/일시정지 [5/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 본체 코딩 [6/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 본체 코딩 Cont. [6/?]
[강좌로 보는 서비스 프로그래밍] 뼈다귀 서비스 코드 [7/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 디버깅 [8/?]
[강좌로 보는 서비스 프로그래밍] 내컴에 누가 왔는지 다알아 [9/?]
[강좌로 보는 서비스 프로그래밍] 환영 인사 [10/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 동작 순서 [11/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 종료 [12/?]

 
지금 까지의 진행한 내용을 정리하면서...

요번에 진행할 때는 좀 쓸만한 샘플을 제작해서 올려보려고 하였습니다. 하지만, 지금까지 사용한 샘플코드가 실무에 바로 적용할 수 있을 만큼
제대로 정리가 되지 못했더군요. 플젝만 복사해서 필요할 때 바로 사용할 수 있을 정도의 기본적인 정리를 하고 다음 순서를 진행하고자,
이번 장에서는 사용된 코드를 정리만 하고 넘기도록 하겠습니다. ^^;;;
(참고로 말씀드리면, VS 6.0과 VS2008 로 테스트 되었으며, 프로젝트도 해당 2가지 버전으로 빌드 가능하도록 구성되어 있습니다.)
(그리고, 여전히 땡땡 거리는 서비스입니다. -_-;;;)



프로젝트의 구성...


기본적인 서비스 골격을 가진 main.cpp
실제로 서비스 프로그래밍을 하면서 내부적으로 갖추어야할 기본적인 main() 함수부터 서비스 메인, 서비스 이벤트 핸들러 및 초기 구성에 필요한
모든 내용을 가지고 있는 소스입니다. 소스에는 아래의 6개 함수가 포함되어 있습니다.

main() 콘솔 응용 프로그램의 엔트리 함수 입니다. 전달받은 파라미터를 처리하는 기능을 담당하고, 필요한 경우 도움말을 보여줍니다.
service_main() 서비스에 등록되는 엔트리 함수 입니다. main에서 StartServiceCtrlDispatcher() 함수에 의하여 등록됩니다.
service_handler() 서비스의 이벤트를 받는 함수입니다. service_main 에서 RegisterServiceCtrlHandlerEx 함수에 의하여 등록됩니다.
EventServiceStop() 서비스가 중지되거나 종료될 때 호출되는 이벤트 함수입니다. service_handler 에서 호출됩니다.
EventServicePause() 서비스가 일시 중지될 때 호출되는 이벤트 함수입니다. service_handler 에서 호출됩니다.
EventServiceContinue() 일시중지된 상에서 서비스가 재개될 때 호출되는 이벤트 함수입니다. service_handler 에서 호출됩니다.

서비스 시스템을 관리하는데 필요한 기능을 가진 servicemgr.h 와 servicemgr.cpp
main.cpp 소스는 내부적으로 순수하게 서비스의 기능 구현에 필요한 루틴을 처리한다면, servicemgr 은 실제 서비스를 등록, 제거, 시작, 종료
등과 같은 서비스를 매니징하는데 필요한 모든 기능을 내부적으로 구현하고 있습니다. 해당 기능을 이용하면, services.msc 와 같은 매니저를
이용하지 않고서도 코딩으로 모든 서비스를 제어할 수 있습니다.

서비스에 필요는 하지만, 서비스 자체의 기능과는 먼 유틸리티 serviceutil.h 와 serviceutil.cpp
서비스는 응용 프로그램과는 다른 레이어에서 구동하고, LocalSystem 권한을 가진상태에서 동작하기 때문에, 의외로 제약 사항들이 있습니다.
이러한 부분을 처리하고, 터미널과 같은 상태에서의 동작 처리를 위한 몇몇가지 기능을 가지고 있습니다.

주어진 기능을 테스트하기 위한 코드 readme.txt
위에서 나열된 기능을 하나씩 테스트하기 위하여 제작했던 샘플 코드를 소스에 넣어두니, 상당히 지저분해서... ㅎ~ readme.txt 에 모아 두었습니다.

서비스 매니징을 위한 파리미터 처리 및 도움말 ( -help, -? )
소스를 보시면 아시겠지만, 위 프로젝트로 서비스를 제작하면 설치부터 제거까지 해당 실행파일 내부에 모두 탑재하게 됩니다. 이러한 기능은
작성한 서비스 뿐만 아니라, -srvname 이라는 옵션을 이용하여, 다른 서비스 이름을 옵션으로 전달해주면 시스템에 설치되어 있는 모든 서비스에
그대로 적용되도록 구성되어 있습니다.

만약 불필요한 서비스가 시스템에 존재한다면, 이를 제거할 때 아래과 같이 제거할 수 있습니다.
예) 서비스프로그램.exe -srvname=쓰레기서비스 -uninstall
D:\Personal\Kwon\서비스\SRVAPP\Debug>SRVAPP.exe -srvname=TEST -uninstall
Uninstall: [TEST] Success


중지되어 있는 다른 서비스를 프로그램적으로 시작해야 할경우, 서비스 내부 코드를 이용할 수도 있지만 다음과 같이 가능합니다.
(아래의 예를 system(), ShellExecute(), ShellExecuteEx(), CreateProcess() 등을 사용하여 구동시킬 수 있습니다.)
예) 서비스프로그램.exe -srvname=해당서비스 -start
D:\Personal\Kwon\서비스\SRVAPP\Debug>SRVAPP.exe -srvname=WebClient -start
Start: [WebClient] Success


만약 다른 서비스의 상태를 살펴볼 경우 아래와 같이 수행하여 살펴볼 수 있습니다.
예) 서비스프로그램.exe -srvname=다른서비스 -status
D:\Personal\Kwon\서비스\SRVAPP\Debug>SRVAPP.exe -srvname=Browser -status
Selected Service Status
   Service Name         > Browser
   Service Display Name > Computer Browser
   Service Descriptipn  >
Maintains an updated list of computers on the network and supplies this list to
computers designated as browsers. If this service is stopped, this list will not
 be updated or maintained. If this service is disabled, any services that explic
itly depend on it will fail to start.

   Service Start User   > LocalSystem
   Service Module Path  > C:\WINDOWS\system32\svchost.exe -k netsvcs
   Service Dependency   > LanmanWorkstation
   Service Start Type   > SERVICE_AUTO_START
   Service Run State    > SERVICE_RUNNING




서비스의 구동...

서비스를 처음으로 다루어 보는 분들을 위하여, 현재 만들어져 가고 있는 서비스와 이미 구동되고 있는 서비스들의 동작 방식을 다이어 그램으로
설명해 보려 합니다. 여기서 제시하는 방식은 콘솔에서 실행파일 상태로 바로 실행시켜 볼 수도 있도록 구성되어 있으므로, 기존 서비스와는 조금
다른 로직을 타기는 하지만 전체적인 흐름에 따른 차이는 크지 않습니다. 푸른색으로 명시된 함수명들은 main.cpp 에서 설명된 6개의 함수입니다.



나머지 자세한 사항들은 소스를 참조하시면 될꺼구요, 혹시나 서비스를 구현하는데 따른 문제점이나, 샘플로 제작하기 알맞은 내용등이
있으면 리플남겨 주세요.. ^^;;;
(요즘 서버군이 대부분 64비트머신이라, 테스트도 할겸 플젝내부에 64비트 빌드도 추가로 포함시켰습니다.)

SRVAPP.zip
이번 장에서는 서비스의 몸통을 만들기 전에 서비스를 인스톨 하는 부분과 제거하는 부분에 대하여 알아볼 것이다.
서비스를 설계할 때 기능도 당연히 중요하지만 어떤 동작 상태를 주어 기동하게 할것인가에 대하여 생각해 보아야한다.

1. 먼저 서비스 이름이나 설명을 어떻게 설정할 것인가?
   -> 단순한 문제이지만, 서비스 이름이나 실행파일명, 그리고 이에 따른 설명은 해당 서비스가 어떠한 기능을 하게 되는지
       그리고, 사용하는 사람들이 직관적으로 알아 볼 수 있도록 명확하게 하여야 한다.

2. 서비스를 구동 시키는 계정은 어떻게 처리할 것인가?
   -> 보통 아주 일반적으로 디폴트 계정 즉, SYSTEM(Local System Account) 계정을 사용하게 된다. 
       아주 일반 적인 상황이지만, 간혹 Administrator 계정으로 설정해야 하거나, 혹은 특정 Users 계정으로 구동해야 하는
       경우도 없지않다.

       SYSTEM 계정이 아닌 다른 계정을 사용하게 되면 해당 계정에 SeServiceLogonRight 권한을 주어야한다.
       또한 암호가 비어있는 유저일 경우는 이것 외에도 보안 설정을 풀어주어야 한다.

3. 초기 구동시 자동 시작으로 할것인가? 수동 시작으로 할것인가?
   -> 보통 자동시작으로 초기 설정을 확정하는 경우가 많은데... 서비스가 순수하게 독립 기능을 수행할 경우는 모르겠지만
       먼가 연계되는 부분이 존재한다면, 사용자가 시작 시켜주기를 기다려야 할 수도 있다.
       이러한 상황을 무시하고 무조껀 자동 시작으로 해놓으면, 서비스가 구동에 실패하면서 시스템 로그에 서비스 구동 실패
       로그를 쌓을 것이다. 

4. 서비스가 구동에 실패하였을 경우, 어떠한 처리를 할것인가?
   -> 보통 정상적으로 서비스가 구동되기를 기대하지만, 아주 황당한 이유등으로 서비스 구동이 실패할 수 있다.
       주로 겪는 문제가 Network Adapter 장애로 인하여 소켓등에 문제가 생기거나, 과부하로 인해 스토리지가 깨지거나
       AD 서버 장애로 인하여 로그인에 실패하거나 DBMS가 죽어 있거나 .... 등등.. 여러가지 문제가 발생할 수 있다.

5. 특정 서비스에 의존하는 부분이 존재하는가?
   -> 특수 목적용으로 만들어진 서비스중에서는 DBMS에 의존하던가? 무선랜 서비스가 켜져 있어야 한다던가?
       원격의 관리를 위하여 여러 서비스가 활성화 되어 있어야 한다건가.. 

       편하게 이러한 시스템 에러에 대응하여 디펜던시를 걸어 놓으면 가장 편하게 에러 상황을 피해 나갈 수 있다.
       문제는 이렇게 디펜던시를 걸어 놓으면 상위 서비스에 장애가 생겼을 경우, 하위 서비스는 아예 구동 조차도
       되지 않으므로 어떠한 문제가 발생하였는지 전혀 알 수 가 없다는 것이다.

       그래서, 디펜던시를 걸어 놓는 방법은 추천하지 않는다. 특정 서비스에 종속적이라면 서비스가 초기 구동될 때
       해당 서비스들의 구동 상태를 점검하고 꺼져 있는건 시작 시켜주고 문제가 생기면 에러 로깅을 해주고 종료해야 한다.

6. 데스크탑 유저들과 연동해야 하는 부분이 존재하는가?
   -> 서비스는 백그라운드에서 구동되기 때문에 사실상 로그온된 상태의 유저들에게 화면상으로 정보를 전달하는게 불가능하다.
       보통 IPC를 통하여 사용자 프로그램과 연동하지만, 서비스에서 GUI 어플리케이션을 실행 시켜 줄 수도 있다.
 
       데스크탑과 연동이라는 옵션을 이용하면, 이러한 부분을 일부 커버할 수 있지만, 이건 순수하게 Console Session 만 해당한다.
       즉, 터미널 유저에게는 영향을 못미친다는 것이다.

       서비스와 연동하는 GUI에서 특정 정보를 전달하고 종료했을 때, 서비스 구동에 문제가 생기면 로그에만 남겨야할까?


      
머 저렇게 하나씩 준비해서 서비스의 설치단계를 진행하는 사람은 없겠지만, 한번 쯤은 점검해볼 필요는 있지 않을까?
사실상 개발할 때 문제가 되거나 가장 귀찬은 영역이 4, 5, 6 번 영역이다... -_-;;;

다음의 예제 코드는 아주 간단한 서비스 설치 코드이다. 설정을 최소화한 상태에서는 저렇게 쉬울 수가 없다.
SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
   if (hScm == NULL)
       return GetLastError();

   SC_HANDLE hSrv = CreateService(
       hScm,
       "서비스의 고유 이름",
       "서비스의 외부 표시 이름",
       SERVICE_ALL_ACCESS,
       SERVICE_WIN32_OWN_PROCESS,
       SERVICE_AUTO_START,
       SERVICE_ERROR_NORMAL,
       "서비스 실행파일 경로",
       NULLNULLNULL, NULL, NULL); 

   if(hSrv )
       CloseServiceHandle(hSrv);
   CloseServiceHandle(hScm);

자, 아주 쉽게 서비스의 설치가 완료되었다. 간단하게 이름을 주고, 자동시작하고, 에러는 적당히 시스템로그에 남기고 끝이다.
보통 대부분의 서비스의 설치는 저 상태라도 전혀 문제가 없다...

1. 시스템을 관리하기 위하여 특정한 SP_USER 라는게 생성되었다. 모든 동작이 해당 계정에서 처리되며, 각종 레지스트리도 저 유저 계정하에서
    동작되도록 처리해야 한다. 해당 계정으로 만들어진 대부분은 Administrator 의 접근을 배제 시켜 놓았다.

작업의 편의성을 위해서라도 서비스의 구동 계정을 SP_USER 라고 맞출 필요가 방금 생긴것이다. 왜? 방금 생각해 낸것이니까 ㅡ.,ㅡ
그렇다면 SP_USER가 암호를 가지고 있는가? 아닌가에 따라 작업이 또 달라진다.

SP_USER 가 암호를 가진 유저라면 해당 계정에  SeServiceLogonRight 권한만 주면 문제가 없겠지만, 아쉽게도 암호가 비어있을 경우는
또다른 문제를 생성시킨다. 즉, 기본적으로 서비스를 구동시키는 계정은 암호를 비워둘 수가 없다.

그냥 서비스 계정을 SYSTEM 으로 만들면 아주 편한데, 저거 하나 때문에 골머리 썩어보신 분이 있을 겄이다. (없으면 나만 그랬나.. ㅡㅜ);
(물론 서비스 매니저에서 마우스로 클릭해주고, 보안 관리자에서 보안을 손으로 풀어주면 아주 쉽게 해결되는 부분이기도 하다. 아주 아주 쉽다...)
(항상 코딩으로 먼가를 해결해야 할때가 문제인거다 -_-;;;)

서비스 설치에 저런게 있다는거고 모두 저런 자질구래한 것에 신경 쓸 필요는 없다. 필요한 사람에게 아주 가끔 도움이 될 수 있을까?
세세한 설치방법과 에러 처리에 관한 부분은 샘플 코드를 작성하는 부분으로 넘긴다.

자 다음으로는 서비스를 제거해봐야 겠다.
   SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
   if (hScm == NULL)
       return GetLastError();

   SC_HANDLE hSrv = OpenService(hScm, "서비스 이름", SERVICE_ALL_ACCESS);
   if (hSrv == NULL)
       goto ERROR_LABEL;

   if(!DeleteService(hSrv))
       goto ERROR_LABEL;

   ERROR_LABEL:
   if(hSrv)
       CloseServiceHandle(hSrv);
   if(hScm)
       CloseServiceHandle(hScm);
이전 설치와 마찬가지로 아주 쉽다. 더불어 서비스를 제거할 경우는 '서비스 이름' 이라는 단 하나만 알고 있으면 된다.
별도의 에러처리는 실제 프로그램을 하면서 하나씩 진행하면 되므로, 특별한 에러처리나 이런것은 배제하였다.

다음으로는 여기서 중간 중간 설명하던 권한이나, 서비스 상태에서 데스크탑에 GUI를 띄워주거나 이런 잘잘한 기능을 구현한 함수들을
일부 소개한다. 앞으로 가끔 쓰일것이므로, 미리 포스팅하여 참고할 수 있도록 할것이다.



+ Recent posts