KoreanFoodie's Study
[C++ 게임 서버] 5-4. 패킷 직렬화 #1 본문
[C++ 게임 서버] 5-4. 패킷 직렬화 #1
핵심 :
1. 패킷 직렬화란, 포인터 및 가변 데이터 정보를 패킷으로 주고 받을 때 이를 안전하게 저장하고 불러올 수 있도록 파싱하는 기법이다.
2. 가변 데이터의 경우, Offset 과 Count 를 이용해 직렬화를 쉽게 진행할 수 있다.
3. 직렬화된 데이터는 xml 또는 json 포맷으로 세이브 & 로드를 하며 관리하게 된다. xml 은 가독성이 좋을 수 있으나 복잡하며, json 은 형태가 간단하며 파싱 속도가 빠르다.
이번 글에서는 패킷 직렬화에 대해 알아보자. 영어로는 사실 Serialization 인데, 직렬화라고 하니 살짝 어색하게 느껴지기는 한다. 😅
언리얼에서도 Serialization 이라는 용어를 사용하는데, 그 때는 데이터를 안전하고 완전하게 보관하기 위해 Serialization 을 별도로 제공한다는 설명이 있다.
그리고 패킷 직렬화도, 개념적으로는 패킷의 정보를 안전하고 완전하게 저장하고 불러오기 위해 사용한다. 예를 들어, 아래 예시와 같은 정보를 주고 받아야 한다고 가정해 보자.
struct PKT_S_PLAYER
{
uint32 id;
uint16 level;
Player* data;
vector<BuffData> buffs;
};
이때, 만약 패킷의 정보를 json 이나 xml 의 형태로 저장한 후, 다음에 게임을 켰을 때 그대로 불러와 플레이어와 관련된 정보를 세팅한다면 무슨 일이 일어날까?
일단, id 와 level 은 그렇다 쳐도, data 에 해당하는 녀석은 메모리 주소이므로, 크래시가 발생하게 될 것이다. 😂 또한, 가변 데이터인 buffs 에 대해서도 어떻게 저장하면 좋을지에 대한 아이디어가 필요하다.
이런 고민을 해결하기 위해, 직렬화(Serialization) 과정을 거쳐, 데이터가 손실이 되지 않게 저장을 해 주어야 한다.
개념적으로는, 아래의 그림처럼 표현을 할 수 있을 것이다 :
즉, 포인터인 data 나, 가변 데이터인 buffs 같은 녀석은 별도의 영역으로 빼서 저장을 안전하게 해 줄 것이다. 바로 예시를 보자. 이번 글에서는 일단 스트링인 name 은 빼고, 가변 데이터를 어떻게 처리할지만 다루도록 한다 😀
struct PKT_S_TEST
{
struct BuffsListItem
{
uint64 buffId;
float remainTime;
};
uint16 packetSize; // 공용 헤더
uint16 packetId; // 공용 헤더
uint64 id; // 8
uint32 hp; // 4
uint16 attack; // 2
uint16 buffsOffset;
uint16 buffsCount;
bool Validate()
{
uint32 size = 0;
size += sizeof(PKT_S_TEST);
size += buffsCount * sizeof(BuffsListItem);
if (size != packetSize)
return false;
if (buffsOffset + buffsCount * sizeof(BuffsListItem) > packetSize)
return false;
return true;
}
//vector<BuffData> buffs;
//wstring name;
};
일단 가변 데이터인 vector<BuffData> 를 어떻게 처리해야 하는지부터 보자.
여기서는 buffsOffset 과 buffsCount 를 각각 두었다. 그리고 Validate 함수로 해당 패킷의 유효성을 검증하는데...
잘 보면, buffsCount 와 buffsOffset 을 이용해 패킷의 사이즈를 체크함으로써 패킷의 유효성을 간단히 검증하고 있음을 알 수 있다.
즉, PKT_S_TEST 패킷이 담긴 버퍼는 실제로 아래와 같이 생겨먹었을 것이다 :
[ PKT_S_TEST ][BuffsListItem BuffsListItem BuffsListItem]
아, 그리고 한 가지 더 추가할 부분이 있다. 예를 들어, 아래 패킷의 사이즈는 몇 바이트일까?
struct PKT_S_TEST
{
uint64 id; // 8
uint32 hp; // 4
uint16 attack; // 2
};
산술적으로 합하면 8 + 4 + 2 = 14 바이트가 될 것 같지만... 실제로 어셈블리를 확인해 보면, 24 바이트가 되는 것을 알 수 있다! 😮 그 이유는, 현재 프로젝트가 64 비트 환경에서 돌아가다 보니, 기본 정렬단위가 8 바이트이기 때문에 padding 이 추가되기 때문이다.
이것을 막으려면, #pragrma pack(1) 이라는, 1 바이트씩 읽을 것이라는 매크로를 붙여 주어야 한다!
#pragma pack(1)
struct PKT_S_TEST
{
/** 같은 내용 */
};
#pragma pack()
따라서 위처럼 매크로로 감싸주면 되겠다. 😉
그럼 이제 서버에서도 내용을 조금 바꿔주면 된다. ServerPacketHandler 에서 buffs 를 보내는 방식은 아래와 같이 수정될 것이다 :
struct ListHeader
{
uint16 offset;
uint16 count;
};
// 가변 데이터
ListHeader* buffsHeader = bw.Reserve<ListHeader>();
buffsHeader->offset = bw.WriteSize();
buffsHeader->count = buffs.size();
for (BuffData& buff : buffs)
bw << buff.buffId << buff.remainTime;
그럼 ClientPacketHandler 쪽에서는, S_TEST 패킷을 아래와 같이 핸들링하면 된다! 😁
void ClientPacketHandler::Handle_S_TEST(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
if (len < sizeof(PKT_S_TEST))
return;
PKT_S_TEST pkt;
br >> pkt;
if (pkt.Validate() == false)
return;
//cout << "ID: " << id << " HP : " << hp << " ATT : " << attack << endl;
vector<PKT_S_TEST::BuffsListItem> buffs;
buffs.resize(pkt.buffsCount);
for (int32 i = 0; i < pkt.buffsCount; i++)
br >> buffs[i];
cout << "BufCount : " << pkt.buffsCount << endl;
for (int32 i = 0; i < pkt.buffsCount; i++)
{
cout << "BufInfo : " << buffs[i].buffId << " " << buffs[i].remainTime << endl;
}
}
결국 우리의 목적은 패킷을 안전하게 저장하려는 것인데, xml 로 저장한다면 아래와 같을 것이다(json 으로 저장해도 무방하다. 상황에 따라 선택).
<?xml version="1.0" encoding="utf-8"?>
<PDL>
<Packet name="S_TEST" desc="테스트 용도">
<Field name="id" type="uint64" desc=""/>
<Field name="hp" type="uint32" desc=""/>
<Field name="attack" type="uint16" desc=""/>
<List name="buffs" desc="">
<Field name="buffId" type="uint64" desc=""/>
<Field name="remainTime" type="float" desc=""/>
</List>
</Packet>
</PDL>
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 5-6. 패킷 직렬화 #3 (0) | 2023.12.16 |
---|---|
[C++ 게임 서버] 5-5. 패킷 직렬화 #2 (0) | 2023.12.16 |
[C++ 게임 서버] 5-3. Unicode (0) | 2023.12.15 |
[C++ 게임 서버] 5-2. PacketHandler (0) | 2023.12.15 |
[C++ 게임 서버] 5-1. BufferHelper (0) | 2023.12.14 |