DbmSVC를 구현합시다. EH 메신저 솔루션에 DbmSVC 이름의 콘솔 응용 프로젝트를 만드세요. 그리고 이 책에서는 DbmSVC를 구현한 이후에 테스트 프로젝트를 만들고 테스트하는 부분은 다루지 않을게요.
DbmSVC 프로젝트에 Program.cpp 파일을 추가하여 소스를 구현합시다.
#include "..\\Common\\EHMessenger.h" #pragma comment(lib,"ws2_32") #pragma comment(lib,"..\\Debug\\EHPacketLib") #pragma comment(lib,"..\\Debug\\DbmLib") #pragma comment(lib,"..\\Debug\\EHWrapSocketLib") #include <Windows.h> #include <iostream> using namespace std; #pragma warning(disable:4996)
Dbm 서비스에서는 계정 정보를 관리해야 합니다. 여기에서는 파일 매핑을 이용할게요.
#define DBFNAME TEXT("EHDB.dat")
Dbm 서비스에서 관리할 계정 정보를 구조체로 정의합시다.
#define MAX_ID_LEN 256 #define MAX_PW_LEN 256 #define MAX_NAME_LEN 256 #define MAX_ELEMENT 100 struct DBElem { char id[MAX_ID_LEN]; char pw[MAX_PW_LEN]; char name[MAX_NAME_LEN]; int status; };
Dbm 서비스에서 계정 정보를 관리하기 위해 파일 매핑을 사용합시다. 이를 위해 파일 핸들, 파일 매핑 핸들과 매핑 시작 주소와 현재까지 추가한 계정 수를 위한 변수를 선언하세요.
HANDLE hFile; HANDLE hMapping; DBElem *base; int ecnt;
진입점 main 함수에서는 윈속을 초기화한 후 Dbm 서버를 가동하고 윈속을 해제합니다.
void StartDbmServer(); int main() { WSADATA wsadata; WSAStartup(MAKEWORD(2,2),&wsadata); StartDbmServer(); WSACleanup(); return 0; }
Dbm 서버를 가동하는 함수를 작성합시다.
void InitializeDB(); DWORD WINAPI DoIt(LPVOID pin); void StartDbmServer() {
먼저 DB를 초기화합니다. 이 부분은 별도의 함수로 작성합시다.
InitializeDB();
DBM 서버 소켓을 설정합니다.
SOCKET sock = EHWrapSocket::CreateTCPServer(DBM_PORT,5); SOCKADDR_IN clientaddr; int len = sizeof(clientaddr); SOCKET dosock; DWORD ThreadID;
DBM 서버 소켓이 연결을 수락하면 이에 관한 처리는 별도의 쓰레드에서 수행하게 합시다.
while(1) { dosock = accept(sock,(SOCKADDR *)&clientaddr, &len); CloseHandle(CreateThread(0,0,DoIt,(LPVOID)dosock,0,&ThreadID)); } }
DB를 초기화하는 함수를 작성합시다.
void MakeDB(); void InitializeDB() {
먼저 DB 파일을 열기 시도합니다.
hFile = CreateFile(DBFNAME, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
만약 유효하지 않은 파일 핸들이면 DB 파일이 없다는 것이므로 비어있는 DB를 만들어야 합니다. DB를 만드는 부분은 별도의 함수로 작성합시다. 그리고 DB 파일을 만든 후에 파일 열기하세요.
if(hFile == INVALID_HANDLE_VALUE) { MakeDB(); hFile = CreateFile(DBFNAME, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0); }
연 파일 핸들로 파일 매핑 핸들을 생성합니다.
hMapping = CreateFileMapping(hFile,0,PAGE_READWRITE,0,0,0);
파일 매핑 핸들로 전체 영역을 매핑합니다.
base = (DBElem *)MapViewOfFile(hMapping, FILE_MAP_READ|FILE_MAP_WRITE, 0,0,0);
순차적으로 이동하면서 현재 DB에 보관한 계정 개수를 파악합니다.
for(ecnt = 0; ecnt<MAX_ELEMENT; ++ecnt) {
처음으로 상태가 STS_UNREG인 곳을 만나면 빈 곳을 만난 것이므로 반복문을 탈출합니다.
if(base[ecnt].status == STS_UNREG) { break; } } }
비어있는 DB를 만드는 함수를 작성합시다.
void MakeDB() { DBElem emptybase[MAX_ELEMENT]={0};
DB 파일을 생성하여 빈 레코드를 파일에 기록합니다.
hFile = ::CreateFile(DBFNAME,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0); DWORD dw; WriteFile(hFile,emptybase,sizeof(emptybase),&dw,0); }
Dbm 서버 소켓이 연결 수락하였을 때 처리하는 쓰레드 진입점 함수를 작성합시다.
void IdExistProc(SOCKET sock,EHPacket *ep); void AddUserInfoProc(SOCKET sock,EHPacket *ep); void UserStsProc(SOCKET sock,EHPacket *ep); void IsCorrectProc(SOCKET sock,EHPacket *ep); void ChangeStsProc(SOCKET sock,EHPacket *ep); void RemoveUserProc(SOCKET sock,EHPacket *ep); DWORD WINAPI DoIt(LPVOID pin) {
쓰레드 입력 인자로 전달받은 소켓을 형식 변환합니다.
SOCKET sock = (SOCKET)pin;
메시지를 수신합니다.
EHPacket ep(sock);
메시지 종류에 따라 처리하는 프로시져를 호출합니다.
switch(ep.GetMsgId()) { case MID_IDEXIST: IdExistProc(sock,&ep); break; case MID_ADDUSERINFO: AddUserInfoProc(sock,&ep); break; case MID_USERSTS: UserStsProc(sock,&ep); break; case MID_ISCORRECT: IsCorrectProc(sock,&ep); break; case MID_CHANGESTS: ChangeStsProc(sock,&ep); break; case MID_REMOVEUSER: RemoveUserProc(sock,&ep); break; } closesocket(sock); return 0; }
아이디 존재 요청 확인 메시지를 수신하였을 때 처리하는 함수를 작성합시다.
DBElem *FindDBElem(string id); void IdExistProc(SOCKET sock,EHPacket *ep) { cout<<"아이디 존재 요청 확인 메시지 수신"<<endl;
수신한 메시지를 IDExist 형식으로 변환합니다.
IDExist ie(ep);
DB 파일에서 수신한 메시지의 아이디와 같은 레코드를 찾습니다. 이 부분은 별도의 함수로 작성합시다.
DBElem *elem = FindDBElem(ie.GetId()); int result = RES_ID_NOTEXIST;
검색 결과가 유효하면 아이디가 존재하는 것입니다.
if(elem) { cout<<ie.GetId()<<" 아이디 존재"<<endl; result = RES_ID_EXIST; }
그렇지 않으면 아이디가 존재하지 않는 것입니다.
else { cout<<ie.GetId()<<" 아이디 존재하지 않음"<<endl; }
아이디 존재 요청 응답 메시지를 만들어 전송합니다.
IDExistAck iea(result); cout<<"아이디 존재 요청 응답 메시지 전송"<<endl; iea.Serialize(sock); }
DB 파일에서 아이디로 레코드를 찾는 함수를 작성합시다.
DBElem *FindDBElem(string id) {
순차적으로 이동하면서 아이디가 같은 부분을 찾습니다.
for(int i = 0; i<ecnt; ++i) { if(strcmp(base[i].id,id.c_str())==0) { return base+i; } } return 0; }
계정 추가 메시지를 수신하였을 때 처리하는 함수를 작성합시다.
void AddUserInfoProc(SOCKET sock,EHPacket *ep) {
수신한 메시지를 계정 추가 메시지로 변환하고 마지막 레코드 뒤에 수신한 메시지 정보를 기록합니다.
AddUserInfo aui(ep); cout<<aui.GetId()<<" 계정 추가 메시지 수신"<<endl; strcpy(base[ecnt].id,aui.GetId().c_str()); strcpy(base[ecnt].pw,aui.GetPW().c_str()); strcpy(base[ecnt].name,aui.GetName().c_str()); base[ecnt].status = STS_REG; ecnt++; }
계정 상태 요청 메시지를 수신하였을 때 처리하는 함수를 작성합시다.
void UserStsProc(SOCKET sock,EHPacket *ep) { cout<<"계정 상태 요청 메시지 수신"<<endl;
수신한 메시지를 계정 상태 요청 메시지로 변환합니다.
UserSts usts(ep);
수신한 메시지의 아이디가 있는 레코드를 찾습니다.
DBElem *elem = FindDBElem(usts.GetId());
레코드를 찾았으면 레코드의 상태로 결과를 변경합니다.
int result = STS_UNREG; if(elem) { cout<<usts.GetId()<<" 계정 상태 확인"<<endl; result = elem->status; } else { cout<<usts.GetId()<<" 계정을 찾을 수 없음"<<endl; }
계정 상태 요청 응답 메시지를 생성하여 전송합니다.
UserStsAck ustsa(result); cout<<"계정 상태 요청 응답 메시지 전송"<<endl; ustsa.Serialize(sock); }
비밀 번호 일치 확인 요청 메시지를 수신하였을 때 처리하는 함수를 작성합시다.
void IsCorrectProc(SOCKET sock,EHPacket *ep) { cout<<"비밀번호 일치 확인 요청 메시지 수신"<<endl;
수신한 메시지를 비밀번호 일치 확인 요청 메시지로 변환합니다.
IsCorrect ic(ep);
수신한 메시지의 아이디와 같은 레코드를 찾습니다.
DBElem *elem = FindDBElem(ic.GetId()); int result = IDPW_NOTCORRECT; if(elem) {
찾은 레코드의 비밀 번호와 수신한 메시지의 비밀번호가 일치하는지 확인합니다.
if(strcmp(elem->pw,ic.GetPW().c_str())==0) { cout<<"일치"<<endl; result = IDPW_CORRECT; } else { cout<<"불일치"<<endl; } } else { cout<<"오류!!! "<<ic.GetId()<<" 계정을 찾을 수 없음"<<endl; }
비밀 번호 일치 확인 요청 응답 메시지를 생성하여 전송합니다.
IsCorrectAck ica(result); cout<<"비밀 번호 일치 확인 요청 응답 메시지 전송"<<endl; ica.Serialize(sock); }
계정 상태 변경 요청 메시지를 수신하였을 때 처리하는 함수를 작성합시다.
void ChangeStsProc(SOCKET sock,EHPacket *ep) {
수신한 메시지를 계정 상태 변경 요청 메시지로 변환합니다.
ChangeSts csts(ep); cout<<csts.GetId()<<" 계정 상태 변경 요청 메시지 수신"<<endl;
수신한 메시지의 아이디와 일치하는 레코드를 찾습니다.
DBElem *elem = FindDBElem(csts.GetId()); if(elem) {
찾은 레코드의 상태를 수신함 메시지의 상태로 변경합니다.
elem->status = csts.GetStatus(); cout<<csts.GetId()<<" 계정 상태 변경"<<endl; }
수신한 메시지의 아이디와 일치하는 레코드를 찾지 못하는 것은 오류가 있는 것입니다.
else { cout<<"오류!!! "<<csts.GetId()<<" 계정을 찾을 수 없음"<<endl; } }
계정 삭제 요청 메시지를 수신하였을 때 처리하는 함수를 작성합시다.
void RemoveUserProc(SOCKET sock,EHPacket *ep) {
수신한 메시지를 계정 삭제 요청 메시지로 변환합니다.
RemoveUser ru(ep); cout<<ru.GetId()<<" 계정 삭제 요청 메시지 수신"<<endl;
수신한 메시지의 아이디와 일치하는 레코드를 찾습니다.
DBElem *elem = FindDBElem(ru.GetId()); if(elem) {
레코드를 찾았으면 계정 수를 감소합니다.
ecnt--;
맨 뒤에 있던 레코드를 삭제할 레코드에 복사합니다.
memcpy(elem,base+ecnt,sizeof(DBElem));
맨 뒤에 있는 레코드를 초기화합니다.
memset(base+ecnt,0,sizeof(DBElem)); }
만약 레코드를 찾지 못했으면 오류입니다.
else { cout<<"오류!!! "<<ru.GetId()<<" 계정을 찾을 수 없음"<<endl; } }