지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]
[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]
[강좌로 보는 서비스 프로그래밍] 가끔 쓸모있는 관련 함수들 [4/?]
[강좌로 보는 서비스 프로그래밍] 시작/중지/일시정지 [5/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 본체 코딩 [6/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 본체 코딩 Cont. [6/?]
[강좌로 보는 서비스 프로그래밍] 뼈다귀 서비스 코드 [7/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 디버깅 [8/?]
아래의 구조체는 제공되는 이벤트를 문자열로 출력해 보기 위하여 정의 해놓은 부분이다.
다음은 이벤트 핸들러에 해당 이벤트처리 부분을 추가한 것이다.
해당 소스는 MSVS 6.0 과 2008 버전으로 빌드 가능하도록 구성하였다.
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [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
#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 ) 의 정보와 같은 것을 해당 서비스에서
실시간으로 받을 수 있고, 그런 정보를 분석하여 필요한 추가 작업을 진행해 줄 수 있다.
위의 이벤트는 컴퓨터에서 발생하는 세션의 변화를 감지하여 알려준다. 세션의 변화라는 것은 내 컴터에 터미널로 접속하는것
혹은 로그인, 로그오프... 등등등... 현재 모니터링 되고 있는 대상 컴퓨터에 접속정보를 알려주는 것이다.
윈도우즈의 이벤트 뷰어 (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},
};
// 아래의 구조체는 서비스에서 제공받는 이벤트를 시각적으로 출력할 때
// 사용하기 위하여 이벤트와 이름을 매핑하여 놓은 것이다.
////////////////////////////////////////////////////////////////////
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));
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;
}
// 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
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 버전으로 빌드 가능하도록 구성하였다.