광란의슈가슈가룬 2025. 5. 26. 20:05

저번에 버퍼 헬퍼 클래스를 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;
	}
}

이런 식으로 개수를 먼저 입력해주고 데이터를 보내면 된다. 문자열이나 일반적인 배열, 위와 같은 리스트를 보낼 때 이런 방법을 이용할 수 있다. 다른 데이터는 그냥 보내면 되지만 문자의 경우는 인코딩 문제가 있을 수 있기 때문에 다음 글은 인코딩에 대한 글이 될 것이다.

 

여기서 이제 조심해야 될 것이 있다. 항상 명심해야 될 것은 상대가 보내는 모든 정보는 신뢰할 수 없다는 것이다. 항상 해킹, 조작의 여지가 있다고 생각해야되기 때문에 그것을 방지할 장치를 만들어 둬야한다.