2. Packet Handler
저번에 버퍼 헬퍼 클래스를 2개로 나눠서 만들었는데 뭔가 좀 애매한 부분이 있다.
데이터를 파싱할 때 순서를 맞춰야 한다는 것이다. 이름, 체력, 공격력 순서대로 파싱을 했다면 받을 때도 같은 순서로 받아야 한다.
순서가 바뀌게 된다면 이상한 값이 들어오게 될 것이다.
다음 문제점은 패킷의 종류별로 나누지 않았다는 것이다. 실제로 서버를 돌리게 되면 여러 패킷이 올 텐데 패킷의 id에 맞춰 기능을 수행하도록 만들어야 한다.
virtual int32 OnRecvPacket(BYTE* buffer, int32 len) override
{
BufferReader br(buffer, len);
PacketHeader header;
br >> header;
uint64 id;
uint32 hp;
uint16 attack;
br >> id >> hp >> attack;
cout << "ID: " << id
<< " HP: " << hp
<< " Attack: " << attack << endl;
char recvBuffer[4096];
br.Read(recvBuffer, header.size - sizeof(PacketHeader) - 8 - 4 - 2);
cout << recvBuffer << endl;
return len;
}
클라이언트에서 수행하던 이 부분을 이제 PacketHandler부분으로 이식할 것이다.
ClientPacketHandler.h
#pragma once
enum
{
S_TEST = 1
};
class ClientPacketHandler
{
public:
static void HandlePacket(BYTE* buffer, int32 len);
static void Handle_S_TEST(BYTE* buffer, int32 len);
};
ClientPacketHandler.cpp
#include "pch.h"
#include "ClientPacketHandler.h"
#include "BufferReader.h"
void ClientPacketHandler::HandlePacket(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
PacketHeader header;
br >> header;
switch (header.id)
{
case S_TEST:
Handle_S_TEST(buffer, len);
break;
}
}
// 패킷 설계 temp
struct S_TEST
{
uint64 id;
uint32 hp;
uint16 attack;
};
void ClientPacketHandler::Handle_S_TEST(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
PacketHeader header;
br >> header;
uint64 id;
uint32 hp;
uint16 attack;
br >> id >> hp >> attack;
cout << "ID: " << id
<< " HP: " << hp
<< " Attack: " << attack << endl;
}
ServerPacketHandler.h
#pragma once
enum
{
S_TEST = 1
};
class ServerPacketHandler
{
public:
static void HandlePacket(BYTE* buffer, int32 len);
static SendBufferRef Make_S_TEST(uint64 id, uint32 hp, uint16 attack);
};
ServerPacketHandler.cpp
#include "pch.h"
#include "ServerPacketHandler.h"
#include "BufferReader.h"
#include "BufferWriter.h"
void ServerPacketHandler::HandlePacket(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
PacketHeader header;
br.Peek(&header);
switch (header.id)
{
default:
break;
}
}
SendBufferRef ServerPacketHandler::Make_S_TEST(uint64 id, uint32 hp, uint16 attack)
{
SendBufferRef sendBuffer = GSendBufferManager->Open(4096);
BufferWriter bw(sendBuffer->Buffer(), sendBuffer->AllocSize());
PacketHeader* header = bw.Reserve<PacketHeader>();
// id(uint64), 체력(uint32), 공격력(uint16)
bw << id << hp << attack;
header->size = bw.WriteSize();
header->id = S_TEST;
sendBuffer->Close(bw.WriteSize());
return sendBuffer;
}
이렇게 패킷을 관리하는 클래스를 클라이언트측과 서버측에 따로 만들어서 들어오는 패킷을 관리하면 된다.
main.cpp
while (true)
{
SendBufferRef sendBuffer = ServerPacketHandler::Make_S_TEST(1001, 150, 10);
GSessionManager.Broadcast(sendBuffer);
std::this_thread::sleep_for(250ms);
}
메인 서버에서 클라이언트에게 패킷을 보내는 부분이다 굉장히 간단해진 것을 볼 수 있다.
client.cpp
virtual void OnRecvPacket(BYTE* buffer, int32 len) override
{
ClientPacketHandler::HandlePacket(buffer, len);
}
클라이언트에서 패킷을 받는 부분도 그냥 1줄로 바뀌었다.
그럼 이제 문제가 무엇이냐? 가변적인 데이터를 보낼 때이다.
// 패킷 설계 temp
struct BuffData
{
int buffId;
float remainTime;
};
struct S_TEST
{
uint64 id;
uint32 hp;
uint16 attack;
// 가변데이터
vector<BuffData> buffs;
};
이런식으로 사용자가 받은 버프나 디버프를 관리하는 벡터가 있다고 생각해보자. 일반적으로는 그 데이터의 개수를 보내주고 그 개수만큼 읽으면 될 것이다.
ServerPacketHandler.cpp
SendBufferRef ServerPacketHandler::Make_S_TEST(uint64 id, uint32 hp, uint16 attack, vector<BuffData> buffs)
{
SendBufferRef sendBuffer = GSendBufferManager->Open(4096);
BufferWriter bw(sendBuffer->Buffer(), sendBuffer->AllocSize());
PacketHeader* header = bw.Reserve<PacketHeader>();
// id(uint64), 체력(uint32), 공격력(uint16)
bw << id << hp << attack;
// 가변데이터
bw << (uint16)buffs.size();
for (BuffData& buff : buffs)
{
bw << buff.buffId << buff.remainTime;
}
header->size = bw.WriteSize();
header->id = S_TEST;
sendBuffer->Close(bw.WriteSize());
return sendBuffer;
}
ClientPacketHandler.cpp
void ClientPacketHandler::Handle_S_TEST(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
PacketHeader header;
br >> header;
uint64 id;
uint32 hp;
uint16 attack;
br >> id >> hp >> attack;
cout << "ID: " << id
<< " HP: " << hp
<< " Attack: " << attack << endl;
vector<BuffData> buffs;
uint16 buffCount;
br >> buffCount;
buffs.resize(buffCount);
for (int i = 0; i < buffCount; i++)
{
br >> buffs[i].buffId >> buffs[i].remainTime;
}
cout << "BuffCount : " << buffCount << endl;
for (int32 i = 0; i < buffCount; i++)
{
cout << "BuffInfo : " << buffs[i].buffId << ' ' << buffs[i].remainTime << endl;
}
}
이런 식으로 개수를 먼저 입력해주고 데이터를 보내면 된다. 문자열이나 일반적인 배열, 위와 같은 리스트를 보낼 때 이런 방법을 이용할 수 있다. 다른 데이터는 그냥 보내면 되지만 문자의 경우는 인코딩 문제가 있을 수 있기 때문에 다음 글은 인코딩에 대한 글이 될 것이다.
여기서 이제 조심해야 될 것이 있다. 항상 명심해야 될 것은 상대가 보내는 모든 정보는 신뢰할 수 없다는 것이다. 항상 해킹, 조작의 여지가 있다고 생각해야되기 때문에 그것을 방지할 장치를 만들어 둬야한다.