블로그 이미지
fiadot_old

칼퇴근을 위한 게임 서버 개발 방법론에 대한 심도있는 고찰 및 성찰을 위한 블로그!

Rss feed Tistory
마이크로소프트웨어 2008. 4. 22. 20:36

메타프로그래밍을 이용한 비동기 RPC 설계와 구현

[Developer Works | 템플릿 메타프로그래밍 활용] 마이크로소프트웨어 2008년 4월

템플릿 메타프로그래밍 활용

비동기 RPC 설계와 구현

온라인 게임 프로젝트를 진행하다 보면 서로간의 정보를 주고받는 메시지(패킷) 처리 부분에 많은 코드가 사용된다. 이는 코드 형태도 유사하고 코드 양도 상당한데 마땅한 방법이 없어 그냥 사용했거나, 여유가 없어서 더 나은 방법을 찾지 못했기 때문일 수도 있다.

이 글에서는 패킷 처리에 할애하는 많은 코드를 줄이고 나아가 템플릿 메타프로그래밍을 통해 좀 더 효율적으로 메시지를 처리하는 방법을 소개한다.

-------------------------------------

이근호 fiadot@gmail.com, http://www.fiadot.com | 현재 인제대학교 컴퓨터공학과에 재학 중이며, 온라인게임 개발업체인 NeonSoft R&D팀에서 서버 엔진을 개발하고 있다. 다양한 언어 중에서도 특히 C++에 많은 관심을 가지고 있고, 기회가 된다면 많은 서버개발자들에게 도움이 될 게임서버 관련 서적을 집필하고픈 꿈을 가지고 있다.

--------------------------------

최소한의 의미를 담고 있는 패킷의 구성은 패킷의 전체 크기, 구분할 수 있는 타입, 부가적인 데이터로 구분해 표현할 수 있다.(패킷을 구분하는 필드는 command, type, id 등 여러 가지 이름으로 부르는데 여기서는 타입이라고 한다)

일반적인 방법

일반적으로 타입에 따라 해당 데이터를 처리 함수로 분기시키는 데 switch나 if-else 구문을 사용하게 된다. 여기서는 장황한 설명보다는 간단한 코드를 살펴보면서 문제점을 짚어 보도록 한다.

<리스트 1> 로그인 패킷의 일반적인 처리 방법

// 헤더

enum PKT_TYPE

{

PKT_REQ_LOGIN, // 로그인 요청(C->S)

PKT_RES_LOGIN,// 로그인 응답(S->C)

PKT_TOTAL// 패킷 타입의 전체 개수

};

struct S_PKT_HEADER

{

int nLen; // 패킷 크기

int nType;// 패킷 타입

};

struct S_PKT_REQ_LOGIN : public S_PKT_HEADER

{

charszID[12];

charszPwd[12];

S_PKT_REQ_LOGIN(char* _szID, char* _szPwd)

{

nLen = sizeof(*this);

nType = PKT_REQ_LOGIN;

strcpy(szID, _szID);

strcpy(szPwd, _szPwd);

}

};

struct S_PKT_RES_LOGIN : public S_PKT_HEADER

{

// 로그인 실패=0, 성공=1, 에러=2

intnRet;

S_PKT_RES_LOGIN(int _nRet)

{

nLen = sizeof(*this);

nType = PKT_RES_LOGIN;

nRet = _nRet;

}

};

class CLogin : public FIASocket

{

// 중략

void Send_Req_Login(char* pszID, char* pszPassword);

void Send_Res_Login(int nRet);

void ReceivePacket(S_PKT_HEADER* pktHdr);

// 중략

};

// 소스

// send

void CLogin::Send_Req_Login(char* pszID, char* pszPassword)

{

S_PKT_REQ_LOGIN data(pszID, pszPassword);

// do something...

Send(&data, data.nLen);

}

// receive

void CLogin::ReceivePacket(S_PKT_HEADER* pktHdr)

{

switch ( pktHdr->nType )

{

case PKT_REQ_LOGIN:

S_PKT_REQ_LOGIN* pkt = (S_PKT_REQ_LOGIN*)pktHdr;

Send_Res_Login(LoginProcess(pkt->szID, pkt->szPwd));

break;

// TODO : 그 외 타입에 따른 처리

}

}

void CLogin::Send_Res_Login(int nRet)

{

S_PKT_RES_LOGIN data(nRet);

// do something...

Send(&data, data.nLen);

}

일반적인 방법의 문제점

여기에 이동에 관련된 패킷을 추가한다고 생각해 보자.

,
TOTAL TODAY