이제 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; }
여기서는 간단하게 단위 테스트를 위한 프로젝트를 만드는 원리만 소개하였습니다.