패킷 직렬화 할 때 크게 2가지 부류가 있다.
Protobuf처럼 임시 객체를 만들어서 직렬화된 버퍼를 역직렬화 하여 임시 객체에 보관하여 데이터에 접근하거나,
FlatBuffer처럼 직렬화를 할 때 버퍼의 앞부분을 저장해서, offset만 파싱하여 버퍼에 바로 접근하거나
이렇게 2가지 분류가 있다.
지금 우리는 Protobuf처럼 임시 객체를 만들어서 역직렬화 된 데이터를 보관해주고 있다.
코드 가독성도 좋고 작업하기에도 편리하지만 컨테이너를 만드는데 불필요한 복사 비용이 생긴다.
그래서 이번에는 데이터를 만들고 읽을 때 임시 객체를 만드는 과정을 건너 뛰고, 버퍼에 있는 내용을 바로 쓰고 읽는 방법에 대해 알아보자.
Handle_S_TEST 함수 수정
//[ PKT_S_TEST ][ BuffeListItem ... ]
void ClientPacketHandler::Handle_S_TEST(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
PKT_S_TEST* pkt = reinterpret_cast<PKT_S_TEST*>(buffer);
if (pkt->Validate() == false)
return;
cout << "ID : " << pkt->id << "HP : " << pkt->hp << "ATK : " << pkt->attack << endl;
PKT_S_TEST::BuffsList buffs = pkt->GetBuffList();
cout << "BuffCount : " << buffs.Count() << endl;
for (int32 i = 0; i < buffs.Count(); i++)
{
cout << "BuffInfo : " << buffs[i].buffId << " " << buffs[i].remainTime << endl;
}
for (auto it = buffs.begin(); it != buffs.end(); ++it)
{
cout << "BuffInfo : " << it->buffId << " " << it->remainTime << endl;
}
for (auto& buff : buffs)
{
cout << "BuffInfo : " << buff.buffId << " " << buff.remainTime << endl;
}
}
패킷 구조
[ PKT_S_TEST ][ BuffeListItem ... ]
기존에는 패킷의 길이가 PKT_S_TEST의 크기 보다 커야 한다는 조건을 붙여서
데이터를 버퍼에서 안전하게 꺼내 썼지만, 버퍼에 바로 접근하게 될 수 있다면 필요없기 때문에 지워주었다.
PKT_S_TEST* pkt = reinterpret_cast<PKT_S_TEST*>(buffer);
포인터로 PKT_S_TEST 포인터를 만들고 버퍼를 PKT_S_TEST로 캐스팅 해주자.
(버퍼의 시작 주소가 PKT_S_TEST로 동일하기 때문에 가능)
이렇게 사용하더라도 만약 패킷 조작이 있었더라면 이 패킷은 사용하면 안된다.
확실한건 패킷 헤더 사이즈(4바이트) 만큼은 안전하게 사용할 수 있지만
그 뒤에 데이터들은 정상적인지 아닌지 모른다.
그러니 Validate함수에 조건문을 하나 더 달아주도록 하자.
bool Validate()
{
...
if (packetSize < size)
return false;
...
}
받은 패킷의 사이즈가 PKT_S_TEST 보다 작으면 문제가 있다.
받은 패킷의 사이즈가 PKT_S_TEST보다 클 경우 패켓헤더를 넘어서 데이터가 있다는 뜻이므로
안전하게 데이터에 접근할 수 있을 것 이다.
가변 데이터도 굳이 벡터를 이용해 임시 객체를 만들어서 꺼내 쓰지 않고,
버퍼에서 버프의 위치와 개수를 알고있으니 바로 꺼내 쓸 수 있을 것이다.
이를 위한 템플릿 클래스를 만들어 주자.
가변 데이터를 버퍼에서 바로 읽기 위한 PacketList
template<typename T>
class PacketList
{
public:
PacketList() : _data(nullptr), _count(0){}
PacketList(T* data, uint16 count) :_data(data), _count(count) {}
T& operator[](uint16 index)
{
ASSERT_CRASH(index < _count);
return _data[index];
}
uint16 Count() { return _count; }
//ranged-base for 지원
PacketIterator<T, PacketList<T>>begin() { return PacketIterator<T, PacketList<T>>(*this, 0); }
PacketIterator<T, PacketList<T>>end() { return PacketIterator<T, PacketList<T>>(*this, _count); }
private:
T* _data;
uint16 _count;
};
T& operator[](uint16 index)
{
ASSERT_CRASH(index < _count);
return _data[index];
}
[] 오퍼레이터를 통해 배열 문법으로 인덱스에 접근해 데이터를 가져온다.
PacketList 클래스는 어디까지나 가변데이터의 시작 주소를 인자로 받아와서 원하는 인덱스의 데이터를 반환해줄 뿐이다.
데이터를 복사하는게 아니다!!
#pragma pack(1)
struct PKT_S_TEST
{
...
//우리가 만든 PacketList를 통해 데이터를 꺼내 씀.
using BuffsList = PacketList<PKT_S_TEST::BuffListItem>;
//버프리스트의 시작 주소를 얻는 함수
BuffsList GetBuffList()
{
BYTE* data = reinterpret_cast<BYTE*>(this);
//buffsOffset만큼 이동
data += buffsOffset;
return BuffsList(reinterpret_cast<PKT_S_TEST::BuffListItem*>(data), buffsCount);
}
...
};
#pragma pack()
위에서 만든 PacketList를 통해 가변데이터의 시작 주소를 가져오는 함수를 만들어 주자.
BYTE* data = reinterpret_cast<BYTE*>(this);
서버로 부터 받은 패킷의 크기를 1바이트 단위로 캐스팅 해준다.
data += buffsOffset;
현재 패킷 크기에 서버에서 받은 가변데이터의 오프셋만큼 주소를 옮긴다.
return BuffsList(reinterpret_cast<PKT_S_TEST::BuffListItem*>(data), buffsCount);
가변데이터의 오프셋만큼 주소를 옮겼으면 현재 위치가 가변데이터의 시작 주소이기 때문에,
PacketList 생성자의 인자로 data와 버프 갯수를 넣어준다.
이렇게 PacketList를 만들어서 사용하게 되면
만약 버프의 개수가 100개가 있다고 해도, 100개를 전부 복사해서 사용하는게 아니라
간단하게 시작 주소만 가져와서 100개의 데이터에 접근할 수 있게 된다.
예전처럼 임시객체를 만들고 버퍼에서 일일이 꺼내 줄 필요없이,
우리가 만들어준 []오퍼레이터를 통해 바로 접근해서 꺼내 쓸 수 있다.
PacketIterator
이제 가변 데이터도 바로 버퍼에서 읽을 수 있게 되었지만,
조금 아쉬운 점은 기존의 벡터는 아래처럼 다양한 형식으로 활용할 수 있었지만,
우리는 배열 문법만 지원하고 있기 때문에 아래와 같이 사용할 수 없단 점이 있다.
그러니 사용할 수 있도록 PacketIterator를 추가 해주자.
template<typename T, typename C>
class PacketIterator
{
public:
PacketIterator(C& container, uint16 index) :_container(container), _index(index) {}
bool operator!=(const PacketIterator& other)const { return _index != other._index; } //같은지 비교
const T& operator*()const { return _container[_index]; }
T& operator*() { return _container[_index]; }
T* operator->() { return &_container[_index]; }
PacketIterator& operator++() { _index++; return *this; }
PacketIterator operator++(int32) { PacketIterator ret = *this; ++_index; return ret; }
private:
C& _container;
uint16 _index;
};
이렇게 iterator로도 접근이 가능하고 ranged-base for 도 지원이 된다.
'패킷 직렬화' 카테고리의 다른 글
패킷 자동화(1) (0) | 2023.07.14 |
---|---|
패킷 직렬화(3) (0) | 2023.06.09 |
패킷 직렬화 (1) (0) | 2023.05.19 |
Unicode (0) | 2023.05.12 |
Packet Handler (0) | 2023.04.28 |