2010년 8월 30일 월요일

패킷 제네레이터 기본 컨셉

저번에는 SendPacket 함수까지 알아보았다

이제는 받기 함수를 소개할 차례인데

참 어려운 고민이 많았다

일단 서버를 생각한다면  IOCP , select, epoll , kqueue 등등  다양한 소켓이 존재했고
그리고 소켓을 캡슐화한 다른 형태의 라이브러리까지 생각한다면 ,
그런것을 고려해 코드를 생성하기는 매우 어려운 일이었다

게다가  Send쪽은 SendPacket은  void* 와 size 를 인자로  하나의 함수만 어플리케이션쪽에서
정의해 주면 끝났지만

받는쪽은 반대로 void* 와 size 를 받아
패킷을 분석해  PT_INTERFACE로 정의되어 있는 구조체를 인자에 맞게 함수를 호출해줘야 했기때문에
더더욱 쉽게 자동화 하긴 어려웠다

그래서  가장 덜 손이 갈수 있겠다 싶은 방법을 생각해 보았다

먼저 해야할것이

 소켓에서 데이타를 받은 데이터를 stream에 입력하고 enumID에 맞게 PT_INTERFACE에 정의한
구조체로 변환 하는 작업이다

예를 들면 이러한 함수 일것이다

// 이함수는 AC_LogIn 이 데이터가 어떻게 쓰일지 모르기에 , 선언만 되어 있음
void ProcessPacket(SOCKET_ID socketID, const AC_LogIn& ptData);

void RecvPacketAC_LogIn(SOCKET_ID socketID,int8* pBuffer,uint32 bufferSize)
{
AC_LogIn ptData;
StreamReaderSelfBuffer stream(pBuffer,bufferSize);
stream & ptData;
ProcessPacket(socketID,ptData);
}

여기서 보면  내부에서 ProcessPacket 함수가 다시 호출되는데 이것은 스트림에서 구조체로
변환한다음  이 구조체를 사용하는 함수를 호출해주는 역활이다

이 구조체로 무엇을 할지 모르기때문에  이 함수의 구현은 어플리케이션 단에서 해줘야한다
그러므로 패킷 생성기에서 생성된 코드를 보면  선언만 되어 있고 구현은 없다

마치 SendPacket를 어플리케이션 단에서 다시 정의해줬던것과 같은것이다
따라서 저러한 함수를 어플리케이션 단에서 구현해줘야 한다  

자 그럼...  어떻게 저러한 함수가 호출하도록 만들것인가를 살펴보자

일단 저함수를 호출할수 있도록 Map에 담아 가져오기로 한다
enumID 를 키로하고   void (SOCKET_ID ,int8* ,uint32 ) 의 함수포인터를 가지는 Map 가져오기로 하는것이다

void GetFuncMap_gene_recv_PT_AC_Data(RecvPacketMap& ptRecvMap);

그럼 이제 이 Map 담긴 함수를 호출해야 하는데   아주 간략하게 코드로  보자면 이렇게 될것이다


// 스트림을 구조체로 바꿔 준후   구조체를 사용하는 함수를 호출해주는 Map얻어오기
// 자동 생성된 코드에서 이 함수가 선언되어 있다
GetFuncMap_gene_recv_PT_AC_Data(g_funcRecvMap);


//------------------------------------------------------------------------------
// 소켓으로부터 버퍼 얻어오기  ( 간단히 만든거므로 딴지 금지 )
// 전체 크기 얻어 오기
uint32 totalSize;
ptEnumType enumID;
int recvSize = recv(g_sockfd,(char*)&totalSize,HEADER_SIZE,0);
if(recvSize <= 0)
 break;

// enum 얻어오기
PG_ASSERT(recvSize == HEADER_SIZE);
recvSize = recv(g_sockfd,(char*)&enumID,PT_ENUM_TYPE_SIZE,0);
if(recvSize <= 0)
 break;

PG_ASSERT(recvSize == PT_ENUM_TYPE_SIZE);

// 데이타 버퍼 얻어오기
char recvBuf[MAX_PACKETLEN];
uint32 ptSize = totalSize - PT_ENUM_TYPE_SIZE;
recvSize = recv(g_sockfd,recvBuf,ptSize,0);
if(recvSize <= 0)
 break;

PG_ASSERT(recvSize == ptSize);
//------------------------------------------------------------------------------


// 위 루틴으로부터  enumID 와 버퍼  버퍼크기를 얻어올수 있었다

try
{
// check enumID valid
RecvPacketMap::iterator it = g_funcRecvMap.find(enumID);

if( it != g_funcRecvMap.end())
g_funcRecvMap[enumID](g_sockfd,recvBuf,ptSize);
}
catch(StreamExceptionUnderflow* )
{
// 패킷 Stream Underflow
}

g_funcRecvMap   함수포인터 맵에 enumID를 넘겨주면
위에서 스트림을 구조체로 변환해주는  함수포인터를 넘겨 받게 된다

그리고 그 넘겨 받은 함수에  버퍼와 크기를 넘겨주면 자동으로  
( g_funcRecvMap[enumID](g_sockfd,recvBuf,ptSize); )

위에서 말한
void ProcessPacket(SOCKET_ID socketID, const AC_LogIn& ptData);
이러한 함수가 호출된다

단 패킷 생성기에서는 선언만 만들기 때문에 구현은 어플리케이션단에서
정의해주어야 하는것이다

자동으로 많은것을 만들려주다보면  소켓 구조에 의존성을 가지게 되어
재활용율이 떨어지게 되므로  이정도까지가 최선이라 생각이 되었다

필요하다면  코드를 수정해 더  필요한만큼 만들어내면  될것이지만
가장 최적으로 재활용 될만한 수준에서 공개하는게 바람직할거 같아 여기까지 해주는 것으로
일단락 하였다

참 그리고 패킷 제네레이터가 해줘야 할 중요한 작업이 하나더 있는데
PT_INTERFACE의 내용 구성물이 바뀔때마다 ( 멤버 데이터의 순서 바뀜 혹은 추가 삭제 )  
버전업을 시켜 줘야 한다 그래서 버전이 다른 서버/클라이언트 간에 통신을 막아주어야 한다

예전 XX 게임에서는 빌드 할때마다 버전업을 시켜 , 클라이언트만 빌드해도 버전업이되어  
서버를 내리고 다시 버전업 시킨 서버를 올려야하는 시스템이 있었다

프로토콜이 바뀌지 않는 이상 그럴 필요는 없으므로 버전은 프로토콜이 바뀔때만 올라가야한다
따라서 패킷 제네레이터는 PT_INTERFACE의 스트림에
담는 구조가 바뀌었는지 파악하기때문에
자동으로 프로토콜 버전업 관리도 하게 된다  
----------------------------------------

 다음버전에는 좀더 최적화된 버전으로 코드를 생성할수도 있게 옵션을 추가 할 예정이다
 
-------

참고 사항 :

스트림 클래스를 boost의 serialization을 쓰려 했으나 너무 무겁고 ,  파일 쓰기에 중심을 맞춘거 같아
스트림 코드를 재작성하였다  ,  그리고 제가 주말에만 취미로 작업을 하는데
일요일 회사 문이 잠겨 있어 T_T    문서화 작업을 끝내지 못해 공개를 조금 미루게 될거 같다


댓글 1개:

  1. std::vector 와
    std::string 은

    내부적으로 동적할당을 하기 때문에 정적할당보다 오버헤드가 높습니다.

    네트윅의 환경이라면 대단히 빠른 주기로 패킷을 주고 받는 상황이 될 텐데

    그때마다 동적할당으로 패킷을 만들어서 보내면 서버가 곤란할 것 같은데 어떤가요

    패킷 생성기
    #define PT_INTERFACE struct
    #define PG_VECTOR std::vector


    // 이것이 패킷 정의다
    PT_INTERFACE CS_LOG_IN
    {
    String id;
    String password;
    PG_VECTOR itemList;
    }
    패킷 스트림
    class PacketStream
    {
    PacketStream& operator << ( int value );
    PacketStream& operator >> ( int value );
    char m_buffer[1024];
    }

    보낸 사람 : han_juho@naver.com

    답글삭제