7.5.5 EH 메신저 – DbmSVC 구현 [TCP/IP 소켓 프로그래밍 with 윈도우즈]

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;
    }
}