[C++] Packet Handler

2023. 7. 8. 21:44·Study/C++ & C#

클라이언트와 서버는 많은 종류의 패킷으로 통신하게 될 것이다.

각 패킷 ID(OPCODE) 별로 일일이 분기시키고, 반복된 코드를 계속 작성하게 된다면 매우 비효율적일 것이다.

패킷을 보다 효율적으로 관리할 수 있게 해 줄 핸들러 클래스를 작성한다.

 

1. 클래스 작성

서버 클라 양쪽에 핸들러를 만들어 둘 것이다.

 

1-1. Client

#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);
};

ID 열거형의 네임 컨벤션은 아래와 같다.

  • S_TEST
      - (S)erver에서 "보내는" Test 패킷
  • 클라이언트에서 TEMP 패킷을 보낸다면?
      - C_TEMP가 될 것이다.

 

각 함수는 아래와 같은 역할을 한다.

  1. HandlePacket()
      - 패킷 헤더에 있는 ID로 분기해 해당 패킷에 맞는 처리 함수로 보낸다.
      - FF14에서 유사한 일을 하는 함수를 IDA를 통 보면 분기가 800개 이상이나 된다. 그렇게 많이 필요한가?
  2. Handle_S_TEST()
      - 테스트 패킷을 받아 처리해 줄 함수다.

이제 함수를 구현해 보자.

 

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;
	}
}

버퍼에서 헤더를 분리하고 그 ID를 이용해 분기하도록 한다.

 

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 << " ATT : " << attack << endl;
}

순서대로 데이터를 뽑아서 출력하게 했다.

 

 

1-2. Server

클라이언트와 차이가 없다고 볼 수 있다.

#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);
};

아주 똑같다.

차이점이라면 서버에선 패킷을 만들어 보내야 하기 때문에 이름과 역할이 조금 다르다는 것 정도.

 

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; // 1 : Test Msg

	sendBuffer->Close(bw.WriteSize());

	return sendBuffer;
}

기존에 패킷을 만드는 데 사용했던 코드를 그대로 재활용했다.

순서대로 데이터를 넣고 포장하고 보낸다.

 

 

2. 추가 사항 반영

새로운 클래스가 생겼으므로 이를 기존 코드에 반영해야 한다.

 

virtual void OnRecvPacket(BYTE* buffer, int32 len) override
{
	ClientPacketHandler::HandlePacket(buffer, len);
}

패킷 수신에 대해선 서버도 동일한 처리를 하게 된다.

HandlePacket()이 알아서 Handle_S_TEST() 또는 Make_S_TEST()로 넘겨줄 것이다.

 

 

while (true)
{
	SendBufferRef sendBuffer = ServerPacketHandler::Make_S_TEST(1001, 100, 10);
	GSessionManager.Broadcast(sendBuffer);

	this_thread::sleep_for(250ms);
}

서버에서 Broadcast 하는 부분은 위와 같이 정리된다.

기존의 길었던 코드가 Make_S_TEST()하나로 매우 깔끔하게 정리됐다.

 

동작이 잘 된다면 이전에 숙제로 남겨뒀던 가변 데이터 처리에 대해 고민해 봐야 한다.

 

3. 가변 데이터 처리

RPG엔 버프라는 개념이 있으니 이걸로 테스트 할 수 있겠다.

버프나 디버프가 얼마나 걸려 있을지 알 수가 없으니 가변 길이의 데이터가 된다.

 

먼저 버프 정보를 저장할 구조체가 필요하다.

struct BuffData
{
	uint64 buffId;
	float remainTime;
};

버프의 ID와 남은 시간을 가질 것이다.

이 버프 데이터는 벡터로 한 곳에 모아둘 것이고, 패킷이 버프 데이터들도 같이 보내야 하기 때문에 함수를 수정한다.

 

// 자주 사용할 것이므로 하나의 함수의 형태로 둔다
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; // 1 : Test Msg

	sendBuffer->Close(bw.WriteSize());

	return sendBuffer;
}

버프 데이터의 벡터를 인자로 받도록 하고

앞의 데이터를 다 뽑아 먹었다면 버퍼에 순서대로 버프 데이터를 넣는다. 가장 먼저 버프의 개수를 넣어줬다.

 

당연히 받는 쪽도 버프 데이터를 받을 수 있게 수정해 주어야 한다.

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 << " ATT : " << attack << endl;

	vector<BuffData> buffs;
	uint16 buffCount;
	br >> buffCount;

	buffs.resize(buffCount);
	for (int32 i = 0; i < buffCount; i++)
	{
		br >> buffs[i].buffId >> buffs[i].remainTime;
	}

	cout << "BufCount : " << buffCount << endl;
	for (int32 i = 0; i < buffCount; i++)
	{
		cout << "BufInfo : " << buffs[i].buffId << " " << buffs[i].remainTime << endl;
	}
}

데이터를 갖고 있을 임시 벡터와 버퍼의 개수를 갖고 있을 변수를 선언했다.

버프의 개수만큼 벡터를 Resize해 줬고, 임시 벡터에 순서대로 데이터를 넣어준다.

그리고 그 데이터들을 출력하게 했다.

 

이제 서버에서 데이터를 보내는 부분을 수정해야 한다.

while (true)
{
	vector<BuffData> buffs{ BuffData {100, 1.5f}, BuffData{200, 2.3f}, BuffData {300, 0.7f } };
	SendBufferRef sendBuffer = ServerPacketHandler::Make_S_TEST(1001, 100, 10, buffs);
	GSessionManager.Broadcast(sendBuffer);

	this_thread::sleep_for(250ms);
}

버프 데이터를 가지는 벡터를 생성하고 그걸 Make_S_TEST()에 넘겨줬다.

 

 

4. 실행

지금은 스트레스 테스트를 하지 않을 것이기 때문에 클라 개수를 1개로 고쳐놓았다.

 

모든 데이터가 정상적으로 전송되고 출력됨을 알 수 있다.

'Study > C++ & C#' 카테고리의 다른 글

[C++] Packet Serialization  (0) 2023.07.12
[C++] Unicode / Encoding  (0) 2023.07.10
[C++] Buffer Helpers  (0) 2023.07.07
[C#] PingPlugin  (0) 2023.07.06
[C++] Packet Session  (0) 2023.07.04
'Study/C++ & C#' 카테고리의 다른 글
  • [C++] Packet Serialization
  • [C++] Unicode / Encoding
  • [C++] Buffer Helpers
  • [C#] PingPlugin
BVM
BVM
  • BVM
    E:\
    BVM
  • 전체
    오늘
    어제
    • 분류 전체보기 (168)
      • Thoughts (14)
      • Study (69)
        • Japanese (3)
        • C++ & C# (46)
        • Javascript (3)
        • Python (14)
        • Others (3)
      • Play (1)
        • Battlefield (1)
      • Others (11)
      • Camp (73)
        • T.I.L. (57)
        • Temp (1)
        • Standard (10)
        • Challenge (3)
        • Project (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • 본 블로그 개설의 목적
  • 인기 글

  • 태그

    Dalamud
    Selenium
    docker
    c#
    OSI
    C++
    프로그래머스
    7계층
    스타필드
    Python
    서버
    포인터
    네트워크 프로그래밍
    JS
    Server
    IOCP
    클라우드
    bot
    FF14
    암호화
    Network
    Asio
    베데스다
    boost
    네트워크
    로깅
    discord
    db
    discord py
    cloudtype
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
BVM
[C++] Packet Handler
상단으로

티스토리툴바