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

이번에는 서비스 메인에 등록된 이벤트 핸들러에 대하여 진행해 보고자 한다.
서비스에서 제공 받을 수 있는 이벤트의 목록은 다음과 같다.
#define SERVICE_CONTROL_STOP                   0x00000001
#define SERVICE_CONTROL_PAUSE                  0x00000002
#define SERVICE_CONTROL_CONTINUE               0x00000003
#define SERVICE_CONTROL_INTERROGATE            0x00000004
#define SERVICE_CONTROL_SHUTDOWN               0x00000005
#define SERVICE_CONTROL_PARAMCHANGE            0x00000006
#define SERVICE_CONTROL_NETBINDADD             0x00000007
#define SERVICE_CONTROL_NETBINDREMOVE          0x00000008
#define SERVICE_CONTROL_NETBINDENABLE          0x00000009
#define SERVICE_CONTROL_NETBINDDISABLE         0x0000000A
#define SERVICE_CONTROL_DEVICEEVENT            0x0000000B
#define SERVICE_CONTROL_HARDWAREPROFILECHANGE  0x0000000C
#define SERVICE_CONTROL_POWEREVENT             0x0000000D
#define SERVICE_CONTROL_SESSIONCHANGE          0x0000000E

오늘은 그 중에서 SERVICE_CONTROL_SESSIONCHANGE 이벤트에 대하여 알아보자.
위의 이벤트는 컴퓨터에서 발생하는 세션의 변화를 감지하여 알려준다. 세션의 변화라는 것은 내 컴터에 터미널로 접속하는것
혹은 로그인, 로그오프... 등등등... 현재 모니터링 되고 있는 대상 컴퓨터에 접속정보를 알려주는 것이다.

윈도우즈의 이벤트 뷰어 (eventvwr.msc) 를 열어보면 나오는 보안 페이지 (security ) 의 정보와 같은 것을 해당 서비스에서
실시간으로 받을 수 있고, 그런 정보를 분석하여 필요한 추가 작업을 진행해 줄 수 있다.


아래의 구조체는 제공되는 이벤트를 문자열로 출력해 보기 위하여 정의 해놓은 부분이다.
////////////////////////////////////////////////////////////////////
// 아래의 구조체는 서비스에서 제공받는 이벤트를 시각적으로 출력할 때

// 사용하기 위하여 이벤트와 이름을 매핑하여 놓은 것이다.
////////////////////////////////////////////////////////////////////
typedef struct _STNAME
{
    const char* text;
    DWORD id;
} STNAME;

STNAME stname[] = {
    {"",                                      0x00000000},
    {"SERVICE_CONTROL_STOP",                  0x00000001},
    {"SERVICE_CONTROL_PAUSE",                 0x00000002},
    {"SERVICE_CONTROL_CONTINUE",              0x00000003},
    {"SERVICE_CONTROL_INTERROGATE",           0x00000004},
    {"SERVICE_CONTROL_SHUTDOWN",              0x00000005},
    {"SERVICE_CONTROL_PARAMCHANGE",           0x00000006},
    {"SERVICE_CONTROL_NETBINDADD",            0x00000007},
    {"SERVICE_CONTROL_NETBINDREMOVE",         0x00000008},
    {"SERVICE_CONTROL_NETBINDENABLE",         0x00000009},
    {"SERVICE_CONTROL_NETBINDDISABLE",        0x0000000A},
    {"SERVICE_CONTROL_DEVICEEVENT",           0x0000000B},
    {"SERVICE_CONTROL_HARDWAREPROFILECHANGE", 0x0000000C},
    {"SERVICE_CONTROL_POWEREVENT",            0x0000000D},
    {"SERVICE_CONTROL_SESSIONCHANGE",         0x0000000E},
    {"SERVICE_CONTROL_PRESHUTDOWN",           0x0000000F},
};

// 세션 체인지 이벤트에서는 하부 이벤트 타입을 아래 처럼 제공해준다.
STNAME wtsname[] = {
    {"",                                     0x00000000},
    {"CONSOLE_CONNECT",                      0x00000001},
    {"CONSOLE_DISCONNECT",                   0x00000002},
    {"REMOTE_CONNECT",                       0x00000003},
    {"REMOTE_DISCONNECT",                    0x00000004},
    {"LOGON",                                0x00000005},
    {"LOGOFF",                               0x00000006},
    {"LOCK",                                 0x00000007},
    {"UNLOCK",                               0x00000008},
    {"SESSION_REMOTE_CONTROL",               0x00000009},
};

다음은 이벤트 핸들러에 해당 이벤트처리 부분을 추가한 것이다.
CServiceUtility::CSimpleLog FileLog("C:\\srv.log");

DWORD WINAPI service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
   CServiceManager manager;

#ifdef _DEBUG
    if(fdwControl != SERVICE_CONTROL_INTERROGATE)
    {
        fprintf(FileLog, "EVENT> %s\n", stname[fdwControl].text);
        FileLog.flush();
    }
#endif

   switch (fdwControl)
   {
   case SERVICE_CONTROL_PAUSE:
       manager.SetServiceRunStatus(SERVICE_PAUSE_PENDING);
        // 서비스를 일시 중지 시킨다.
       manager.SetServiceRunStatus(SERVICE_PAUSED);
       break;

   case SERVICE_CONTROL_CONTINUE:
       manager.SetServiceRunStatus(SERVICE_CONTINUE_PENDING);
       // 일시 중지 시킨 서비스를 재개한다.
       manager.SetServiceRunStatus(SERVICE_RUNNING);
       break;

   case SERVICE_CONTROL_STOP:
       manager.SetServiceRunStatus(SERVICE_STOP_PENDING);
       // 서비스를 멈춘다 (즉, 종료와 같은 의미)
        // 서비스를 종료하면, service_main 는 절대로 리턴하지 않는다.
        // 그러므로 해제하려면 작업이 있으면 모든것을 이곳에서 처리한다.
       manager.SetServiceRunStatus(SERVICE_STOPPED);
       break;

        // 윈도우에서 세션에 변경 사항이 발생하였을 경우, 발생하는 이벤트.
    case SERVICE_CONTROL_SESSIONCHANGE:
        {
            // 세션에 관련된 이벤트 정보는 아래의 구조체에 복사하여 사용한다.
            WTSSESSION_NOTIFICATION wtsnoti = {0};
            memcpy(&wtsnoti, lpEventData, sizeof(WTSSESSION_NOTIFICATION));

            // 유틸리티 CWTSSession 을 이용하여, 현재 검출된 세션의 정보를 읽어온다.
            // 소스를 열어보면, 검출되는 정보에는 어떤것이 있는지 알 수 있다.
            CServiceUtility::CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, wtsnoti.dwSessionId);

            // FileLog는 서비스는 결과를 화면에 출력할 수 없기 때문에, 아주 심플하게 파일로 찍고자 임시로 만듬.
            // 이벤트 타입 dwEventType 를 이용하여 해당 이벤트때 필요한 작업을 하도록 처리해주면 된다.
            // 현재는 정보만 출력해준다.

#ifdef _DEBUG
            fprintf(FileLog, " WTS[%s <%d>] => IP(%s), Client(%s), Domain(%s), User(%s), WinStation(%s)",
                wtsname[dwEventType].text,
               
wtsnoti.dwSessionId,
               
ws.IPAddress,
               
ws.ClientName,
               
ws.DomainName,
               
ws.UserName, 
                
ws.WinStation);
            FileLog.flush();
#endif
        }

   default:
       break;
   }

   return NO_ERROR;
}

서비스를 띄워 놓고, 로그온/로그오프.. 혹은 외부에서 터미널로 접속하거나 하면 다음과 같이 파일로그에 출력된다.
(필요하다면 시간 정보를 같이 출력해주면 나중에 로깅할 때 도움을 받을 수 도 있다.)
EVENT> SERVICE_CONTROL_STOP
EVENT> SERVICE_CONTROL_SESSIONCHANGE
   WTS[REMOTE_CONNECT <1>] => IP(127.0.0.1), ClientName(JINHO2003), DomainName(), UserName(), WinStation(RDP-Tcp#3)
EVENT> SERVICE_CONTROL_SESSIONCHANGE

   WTS[LOGON <1>] => IP(127.0.0.1), ClientName(JINHO2003), DomainName(JINHO2003), UserName(aaa), WinStation(RDP-Tcp#3)
EVENT> SERVICE_CONTROL_PAUSE

EVENT> SERVICE_CONTROL_STOP
EVENT> SERVICE_CONTROL_SESSIONCHANGE
   WTS[LOGOFF <1>] => IP(127.0.0.1), ClientName(JINHO2003), DomainName(), UserName(), WinStation(RDP-Tcp#3)
EVENT> SERVICE_CONTROL_STOP


해당 소스는 MSVS 6.0 과 2008 버전으로 빌드 가능하도록 구성하였다.

+ Recent posts