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

이제 FendSVC 부터 구현합시다. 서비스를 만들 때는 Win32 응용 프로젝트로 만들어 윈도우를 만들지 않으면 백 그라운드에서 동작하는 서비스를 만들 수 있습니다. 여기에서는 정상적으로 동작하는지 테스트하기 쉽게 콘솔 응용 프로젝트로 만들게요. EH 메신저 솔루션에 FendSVC 이름의 콘솔 응용 프로젝트를 만드세요.

FendSVC 프로젝트에 Program.cpp 파일을 추가하여 소스를 구현합시다.

#include "..\\Common\\EHMessenger.h"

필요한 동적 라이브러리를 참조합니다.

#pragma comment(lib,"ws2_32")
#pragma comment(lib,"..\\Debug\\EHPacketLib")
#pragma comment(lib,"..\\Debug\\RegLib")
#pragma comment(lib,"..\\Debug\\LogLib")
#pragma comment(lib,"..\\Debug\\StsLib")
#pragma comment(lib,"..\\Debug\\DbmLib")
#pragma comment(lib,"..\\Debug\\EHWrapSocketLib")
#include <iostream>
using namespace std;

진입점 main 함수를 작성합시다.

void StartFendServer();
int main()
{

윈속을 초기화합니다.

    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2),&wsadata);

FendServer를 가동합니다.

    StartFendServer();

윈속을 해제화합니다.

    WSACleanup();
    return 0;
}

FendServer 가동 함수를 작성합시다.

DWORD WINAPI DoIt(LPVOID pin);
void StartFendServer()
{

TCP 서버를 생성합니다.

    SOCKET sock = EHWrapSocket::CreateTCPServer(FEND_PORT,5);

    SOCKADDR_IN clientaddr;
    int len = sizeof(clientaddr);
    SOCKET dosock;
    DWORD ThreadID;
    while(1)
    {

클라이언트 연결을 수락합니다.

        dosock = accept(sock,(SOCKADDR *)&clientaddr, &len);

클라이언트와의 통신을 위한 쓰레드를 생성하고 인자로 통신에 사용할 소켓을 전달합니다.

        CloseHandle(CreateThread(0,0,DoIt,(LPVOID)dosock,0,&ThreadID));
    }
}

클라이언트와 통신하는 쓰레드 진입점 DoIt을 작성합시다.

void RegReqProc(SOCKET sock,EHPacket *ep);
void UnRegReqProc(SOCKET sock,EHPacket *ep);
void LogInReqProc(SOCKET sock,EHPacket *ep);
void LogOutReqProc(SOCKET sock,EHPacket *ep);
void KeepAliveProc(SOCKET sock,EHPacket *ep);
DWORD WINAPI DoIt(LPVOID pin)
{

쓰레드 인자로 전달받은 값을 소켓으로 형식 변환합니다.

    SOCKET sock = (SOCKET)pin;

FEndSVC는 Peer의 메시지를 수신하여 이를 비지니스 계층에게 전달하는 역할을 수행합니다. 따라서 Peer로부터 메시지를 수신합니다.

    EHPacket ep(sock);

Peer로부터 받을 수 있는 메시지 종류는 5가지 입니다. 가입 요청, 탈퇴 요청, 로긴 요청, 로그 아웃 요청, KeepAlive 메시지가 있습니다. 메시지 종류에 따라 처리하는 함수를 호출합니다.

    switch(ep.GetMsgId())
    {
    case MID_REGREQ: RegReqProc(sock,&ep);break;
    case MID_UNREGREQ: UnRegReqProc(sock,&ep); break;
    case MID_LOGINREQ: LogInReqProc(sock,&ep); break;
    case MID_LOGOUTREQ: LogOutReqProc(sock,&ep); break;
    case  MID_KEEPALIVE: KeepAliveProc(sock,&ep); break;
    }

처리가 끝나면 소켓을 닫습니다.

    closesocket(sock);
    return 0;
}

가입 요청 메시지 처리 함수를 작성합시다.

void RegReqProc(SOCKET sock,EHPacket *ep)
{

정상적으로 동작하는지 확인하기 쉽게 수신한 메시지 종류를 출력합니다.

    cout<<"가입 요청 수신"<<endl;

가입 요청 메시지는 가입 서비스에 전달해야 합니다. 따라서 가입 서버로 연결합니다.

    SOCKET clisock = EHWrapSocket::Connect(REG_IP,REG_PORT);

수신한 패킷을 가입 서비스와 연결한 소켓으로 전달합니다.

    ep->Serialize(clisock);

가입 요청 시퀀스는 FEndSVC가 RegSVC에게 가입 요청 메시지를 보낸 후 응답 메시지를 수신해야 합니다.

    EHPacket rep(clisock);

가입 서비스와 연결한 소켓을 닫습니다.

    closesocket(clisock);

가입 서비스에서 수신한 메시지를 Peer와 통신하는 소켓으로 전달합니다.

    rep.Serialize(sock);

전달한 메시지 종류를 출력합니다.

    cout<<"가입 요청 결과 전송"<<endl;
}

탈퇴 요청 메시지 처리 함수를 작성합시다.

void UnRegReqProc(SOCKET sock,EHPacket *ep)
{
    cout<<"탈퇴 요청 수신"<<endl;

탈퇴 요청 메시지는 가입 서비스에게 전달합니다. 이를 위해 가입 서비스와 연결합니다.

    SOCKET clisock = EHWrapSocket::Connect(REG_IP, REG_PORT);

탈퇴 요청 메시지를 가입 서비스와 연결한 소켓으로 전달합니다.

    ep->Serialize(clisock);

탈퇴 요청 시퀀스는 탈퇴 요청 메시지를 가입 서비스에게 전달만 합니다.

    closesocket(clisock);
}

로긴 요청은 가입 요청과 비슷합니다. 차이가 있는 부분은 연결하여 전달할 서비스만 다릅니다. 로그 아웃 요청과 KeepAlive는 탈퇴 요청과 비슷합니다.

void LogInReqProc(SOCKET sock,EHPacket *ep)
{
    cout<<"로긴 요청 수신"<<endl;
    SOCKET clisock = EHWrapSocket::Connect(LOG_IP, LOG_PORT);
    ep->Serialize(clisock);
    EHPacket rep(clisock);
    closesocket(clisock);
    rep.Serialize(sock);
    cout<<"로긴 요청 결과 전송"<<endl;
}
void LogOutReqProc(SOCKET sock,EHPacket *ep)
{
    cout<<"로그 아웃 요청 수신"<<endl;
    SOCKET clisock = EHWrapSocket::Connect(LOG_IP, LOG_PORT);
    ep->Serialize(clisock);
    closesocket(clisock);
}
void KeepAliveProc(SOCKET sock,EHPacket *ep)
{
    cout<<"KeepAlive 수신"<<endl;
    SOCKET clisock = EHWrapSocket::Connect(STS_IP,STS_PORT);
    ep->Serialize(clisock);
    closesocket(clisock);
}

단위 테스트를 위해 테스트 프로젝트를 만듭시다. 테스트를 위해 더미 Peer 프로젝트와 더미 서비스 프로젝트를 만들어서 테스트할게요.

더미 Peer 프로젝트에 Program.cpp 소스 파일을 추가하세요. 여기서는 간단한 원리만 소개할게요.

#include "..\\Common\\EHMessenger.h"
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"..\\Debug\\EHPacketLib")

여기에서는 로긴 요청 시퀀스만 테스트할게요.

#pragma comment(lib,"..\\Debug\\LogLib")
#pragma comment(lib,"..\\Debug\\EHWrapSocketLib")
#include <iostream>
using namespace std;

진입점 main 함수를 작성합시다.

void StartDummyPeer();
int main()
{

윈속을 초기화합니다.

    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2),&wsadata);

더미 Peer를 가동합니다.

    StartDummyPeer();

윈속을 해제화합니다.

    WSACleanup();
    return 0;
}

더미 Peer를 가동하는 함수를 작성합시다.

void StartDummyPeer()
{

여기에서는 로긴 요청 메시지를 전송하여 수신하는 테스트를 할게요.

    LogInReq lr("hgd","abc");

FEndSVC에 연결합니다.

    SOCKET clisock = EHWrapSocket::Connect(FEND_IP,FEND_PORT);
    cout<<"로긴 요청 전송 아이디:"<<lr.GetId()<<" 비밀번호:"<<lr.GetPW()<<endl;

로긴 요청 메시지를 전송합니다.

    lr.Serialize(clisock);

로긴 요청 시퀀스에서는 응답을 수신합니다.

    EHPacket ep(clisock);

응답을 수신한 후에 소켓을 닫습니다.

    closesocket(clisock);

수신한 메시지 아이디가 로긴 요청 응답이 맞는지 확인합니다.

    if(ep.GetMsgId() == MID_LOGINRES)
    {

로긴 요청 응답이 맞다면 이를 출력합니다.

        cout<<"로긴 요청 응답 메시지 수신"<<endl;

그리고 결과가 무엇인지 확인하기 위해 로긴 요청 응답 메시지 형식으로 변환합니다.

        LogInRes lres(&ep);

로긴 요청 응답 결과를 출력합니다.

        switch(lres.GetResult())
        {
        case LOGIN_RES_OK:  cout<<"로긴 성공"<<endl; break;
        case LOGIN_RES_NOTEXIST: cout<<"아이디 없음"<<endl; break;
        case LOGIN_RES_LOGGED: cout<<"이미 로긴 중"<<endl; break;
        case LOGIN_RES_NOTCORRECT: cout<<"비밀번호 불일치"<<endl; break;
        }
    }

로긴 요청 응답 메시지가 아니면 오류를 출력합니다.

    else
    {
        cout<<"오류!!! 로긴 요청 응답 메시지를 수신하지 못하였음"<<endl;
    }
}

더미 서비스 프로젝트에 Program.cpp 파일을 추가하여 테스트 로직을 작성합시다.

#include "..\\Common\\EHMessenger.h"
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"..\\Debug\\EHPacketLib")

여기에서는 로긴 요청에 관한 테스트만 진행할 것입니다.

#pragma comment(lib,"..\\Debug\\LogLib")
#pragma comment(lib,"..\\Debug\\EHWrapSocketLib")
#include <iostream>
using namespace std;

진입점 main에서는 더미 서버를 가동합니다.

void StartDummyServer();
int main()
{
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2),&wsadata);
    StartDummyServer();
    WSACleanup();
    return 0;
}

더미 서버 가동 함수에서는 FEndSVC 가동 함수와 비슷합니다. 여기에서는 로긴 요청 시퀀스를 테스트할 것이므로 로그 서비스 서버를 가동하는 형태로 작성합니다.

DWORD WINAPI DoIt(LPVOID pin);
void StartDummyServer()
{
    SOCKET sock = EHWrapSocket::CreateTCPServer(LOG_PORT,5);
    SOCKADDR_IN clientaddr;
    int len = sizeof(clientaddr);
    SOCKET dosock;
    DWORD ThreadID;
    while(1)
    {
        dosock = accept(sock,(SOCKADDR *)&clientaddr, &len);
        CloseHandle(CreateThread(0,0,DoIt,(LPVOID)dosock,0,&ThreadID));
    }
}

처리하는 쓰레드 진입점 함수를 작성합시다.

DWORD WINAPI DoIt(LPVOID pin)
{

쓰레드 인자로 전달받은 값을 소켓 형식으로 변환합니다.

    SOCKET sock = (SOCKET)pin;

먼저 메시지를 수신합니다.

    EHPacket ep(sock);

수신한 메시지가 로긴 요청인지 확인합니다.

    if(ep.GetMsgId() == MID_LOGINREQ)
    {

로긴 요청이 맞다면 로긴 요청 메시지 형식으로 변환합니다.

        LogInReq lr(&ep);

수신한 정보를 확인합니다.

        cout<<"로긴 요청 메시지 수신 아이디:"<<lr.GetId()<<" 비밀번호:"<<lr.GetPW()<<endl;

로긴 요청 응답 메시지를 생성합니다.

        LogInRes lres(LOGIN_RES_NOTCORRECT);

로긴 요청 응답 메시지를 전송합니다.

        lres.Serialize(sock);
        cout<<"로긴 용청 응답 전송"<<endl;
    }

수신한 메시지가 로긴 요청 메시지가 아니면 오류를 출력합니다.

    else
    {
        cout<<"오류!!! 로긴 요청 메시지가 아닙니다."<<endl;
    }
    closesocket(sock);
    return 0;
}

여기서는 간단하게 단위 테스트를 위한 프로젝트를 만드는 원리만 소개하였습니다.

[그림 7.19] FEndSVC 테스트 결과화면