26.01.11 에 남기는 메모

protobuf를 최신 버전으로 받아보려 했는데 abseil과 googletest 의존성 문제를 해결하지 못해서 구버전(21.12)으로 설치했다. 22버전 이후로는 protobuf에 abseil과 googletest가 포함되어있지 않고 없으면 자동으로 설치되도록 만들었다 하는데, 자동으로 설치도 안되고 abseil과 

 

 

 


지난시간까지 패킷을 만들고 직렬화 하는 기술들을 알아봤다.

 

이제는 진짜로 프로젝트에서 사용할 프토토버프를 연동을 시키는 실습을 해보자.

 

굉장히 유명한 프로젝트들이 프토토버프를 사용할 만큼 어지간해서는 이걸 사용하는게 가장 좋은 방법이라고 할 수 있다.

 

 

Protocol Buffers

Protocol Buffers are language-neutral, platform-neutral extensible mechanisms for serializing structured data.

protobuf.dev

  1. Download and install the protocol buffer compiler.
  2. Read the overview.
  3. Try the tutorial for your chosen language.

여기 적힌대로 컴파일러를 다운받고 압축 풀고 이미지처럼 옮겨두자

그리고 위와같이 빈 txt파일로 Protocol.proto 파일을 만들어보자

프로토파일은 프로토파일만의 문법이 있는데 아래 링크를 참조하자. (버전 2, 3이 매우 다르다고 함)

 

 

 

Language Guide (proto 3)

Covers how to use the proto3 revision of the Protocol Buffers language in your project.

protobuf.dev

 

syntax = "proto3";
package Protocol;

message BuffData
{
	uint64 buffId = 1;
	float remainTime = 2;
	repeated uint64 victims = 3;
}

message S_TEST
{
	uint64 id = 1;
	uint32 hp = 2;
	uint16 attack = 3;
	repeated BuffData buffs = 4;
}

syntax : 문법 버전

package : 네임스페이스 이름같은거 (우리가 맘대로 정하는거인듯)

message : 구조체같은거

 = 1 : 순서. 초기값 아님

repeated : 가변데이터

 

이제 프로토파일을 정의했으니 변환기(protoc.exe)를 돌려서 내용을 우리가 원하는 형태로 바꿔야하는데 언어별로 변환이 가능하기 때문에 명확하게 어떤 언어로 변환할지 기입해야한다. 

위 가이드의 Generating Your Classes 파트를 보면 (거의 맨 마지막에있음) 우리의 클래스를 만드는 방법을 알려준다.

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

 

 

이제 GenPacket.bat 파일을 만들고 위의 명령문을 좀 수정해 기입하자.

.bat파일은 유닉스의 .sh파일 느낌인듯?

protoc.exe -I=./ --cpp_out=./ ./Protocol.proto
IF ERRORLEVEL 1 PAUSE

그리고 실행해보면 잘 안된다.

이유는 proto에서 정의할 수 있는 변수값들중에 uint16은 없기 때문. 이 또한 가이드에서 보면 

이런식으로 쓸 수 있는 타입이 주어지는데 uint16이 없는 관계로 그냥 가장 가까운 uint32로 만들어주면 된다고 한다.

프로토파일에서 uint16을 uint32로 바꿔보자.

문제를 해결했으니 다시 bat파일을 실행해보면

두개의 파일이 생성된다.

 

이제 이 파일을 VS에서 게임서버와 더미클라이언트에 아래와 같이 넣어주자.

 

그런데 빌드를 하려하면 에러가 엄청 뜬다.

 

지금까지 한건 PDL.xml을 만들어 준 다음에 변환기로 우리가 만든 패킷을 만들었다.

그런데 내부적으로 프로토버프를 이용하기 위해서 사용해야하는 온갖 기능들을 우리가 지금 사용하지 않았기 때문에 에러가 나는것. (말하는 그대로 적었는데 뭐라는지 잘 모르겠다.)

 

protoc라는 컴파일러를 이용해 파일을 만드는데서 끝나는게 아니라 나머지 기능들. 라이브러리들을 활용할 수 있게끔 라이브러리들을 찾아서 다운받아야 한다는 얘기라고 한다.

Protocol.pb.h 의 내용

 

일단 pch관련 에러부터 지우자.

 

이제 라이브러리를 받아보자.

라이브러리는 어떤건 헤더와 lib파일을 제공하고, 어떤건 소스코드를 던져주는데, 프로토버프는 후자에 해당한다.

 

그리고 라이브러리 디렉터리 구조를 약간 바꿨다. 이건 강의참조 20:00~ 참조

 


이제 구글 프로토버프 c++ 라이브러리를 받자.

CMake로 protobuf c++파일을 라이브러리로 컴파일 하는 과정에서 강의나 질의응답에서 알려주는 해결방법으로 해결이 안됐고, 개인적으로 해결을 시도해봤지만 안돼서 일단 강의자료에 있는 헤더와 lib파일을 사용했다. 아래의 설치방법은 21.12버전의 설치방법이다.

 

 

GitHub - protocolbuffers/protobuf: Protocol Buffers - Google's data interchange format

Protocol Buffers - Google's data interchange format - protocolbuffers/protobuf

github.com

에서 branches - 최근 넘버링 브랜치를 눌러 코드를 다운받는다. (강의자는 3.17.x 나는 21.x) 

그리고 CMAKE를 사용해서 컴파일해야 하니까 CMAKE도 설치하자. (내 환경에 makefile 등 이미 설치된것들이 있어서 다른 것도 설치해야 할 수도 있음)

 

Download CMake

You can either download binaries or source code archives for the latest stable or previous release or access the current development (aka nightly) distribution through Git. This software may not be exported in violation of any U.S. export laws or regulatio

cmake.org

위와같이 설정하고 generate를 클릭

vs버전을 설정해서 finish

 

이러면 에러가 나는데, 아래 두 항목만 체크하고 다시 generate하자.

 

이렇게 하면 설치한 폴더(나는 install)에 파일들이 생긴걸 확인할 수 있다. (Debug와 Release는 추후에 생김)

 

그 중 protobuf.slnx 를 vs로 열어서 (강의에서는 .sln) Debug 버전과 Release 버전으로 빌드해준다.

이렇게 하면 아까 그 폴더(install)에 Debug 폴더와 Release 폴더가 추가되어있다.

Debug 폴더에서 libprotobufd.dll, libprotobufd.lib, libprotobufd.pdb를

Release 폴더에서 libprotobuf.dll, llibprotobuf.lib를 복사해서 서버 프로젝트 폴더에서 Libraries/Libs/Protobuf/ 의 Debug, Release 폴더로 각각 복붙해주자.

그리고 아까 cmake를 했던 폴더(protobuf-21.12)에 src에서 google을 복사해서 우리 서버의 Libraries/Include 폴더로 넣어주자.

 

이제 우리는 proto 파일 -> protoc.exe을 사용해 cc, h파일로 변환 -> cc, h에서 사용하는 함수들의 선언부(google)와 그 함수들의 구현부 라이브러리 (libprotobuf.lib)가 준비됐다.


게임서버

#include "Protocol.pb.h"

보면 직렬화를 위한 다양한 옵션들이 이미 만들어져 있는걸 확인할 수 있다.

Protocol은 이번 강의 초반에 우리가 지은 네임스페이스 이름이고 S_TEST도 우리가 지은 message(구조체같은거) 이름이다.

gameserver.c

/*...*/
#include "Protocol.pb.h"

int main()
{
	/*...*/
	while (true)
	{
		Protocol::S_TEST pkt;

		pkt.set_id(1000);
		pkt.set_hp(100);
		pkt.set_attack(10);

		/*auto m = pkt.mutable_buffs();
		m->Add();
        가변 데이터를 보다 쉽게 넣어줄 옵션도 있다 나중에 써보자.
        */
		{
			Protocol::BuffData* data = pkt.add_buffs();
			data->set_buffid(100);
			data->set_remaintime(1.2f);
			data->add_victims(4000);
		}
		{
			Protocol::BuffData* data = pkt.add_buffs();
			data->set_buffid(100);
			data->set_remaintime(1.2f);
			data->add_victims(1000);
			data->add_victims(2000);
		}

		SendBufferRef sendBuffer = ServerPacketHandler::MakeSendBuffer(pkt);
        /*...*/
	}
	GThreadManager->Join();
}

서버패킷핸들러.h

#pragma once
#include "BufferReader.h"
#include "BufferWriter.h"
#include "Protocol.pb.h"

enum
{
	S_TEST = 1
};

class ServerPacketHandler
{
public:
	static void HandlePacket(BYTE* buffer, int32 len);

	static SendBufferRef MakeSendBuffer(Protocol::S_TEST& pkt);
};

template<typename T>
SendBufferRef _MakeSendBuffer(T& pkt, uint16 pktid)
{
	const uint16 dataSize = static_cast<uint16>(pkt.ByteSizeLong());
	const uint16 packetSize = dataSize + sizeof(PacketHeader);

	SendBufferRef sendBuffer = GSendBufferManager->Open(packetSize);

	PacketHeader* header = reinterpret_cast<PacketHeader*>(sendBuffer->Buffer());
	header->size = packetSize;
	header->id = pktid;

	ASSERT_CRASH(pkt.SerializeToArray(&header[1], dataSize));

	sendBuffer->Close(packetSize);

	return sendBuffer;
}

 

.c

#include "pch.h"
#include "ServerPacketHandler.h"
#include "BufferReader.h"
#include "BufferWriter.h"

/*...*/
SendBufferRef ServerPacketHandler::MakeSendBuffer(Protocol::S_TEST& pkt)
{
	return _MakeSendBuffer(pkt, S_TEST);
}

클라이언트

클라이언트에서는 받은 데이터를 다시 패킷으로 만들어서 제대로 왔는지 확인해보자.

패킷핸들러.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);
};

 

.c

#include "pch.h"
#include "ClientPacketHandler.h"
#include "BufferReader.h"
#include "Protocol.pb.h"

void ClientPacketHandler::HandlePacket(BYTE* buffer, int32 len)
{
	BufferReader br(buffer, len);

	PacketHeader header;
	br >> header;
	//cout << "Packet ID : " << header.id << "Size : " << header.size << endl;

	switch (header.id)
	{
	case S_TEST:
		Handle_S_TEST(buffer, len);
		break;
	}
}

void ClientPacketHandler::Handle_S_TEST(BYTE* buffer, int32 len)
{
	Protocol::S_TEST pkt;

	ASSERT_CRASH(pkt.ParseFromArray(buffer + sizeof(PacketHeader), len - sizeof(PacketHeader)));

	cout << pkt.id() << " " << pkt.hp() << " " << pkt.attack() << endl;

	cout << "Buff Size : " << pkt.buffs_size() << endl;

	for (auto& buf : pkt.buffs())
	{
		cout << "Buffinfo : " << buf.buffid() << " " << buf.remaintime() << endl;
		cout << "Victims : " << buf.victims_size() << endl;
		for (auto& vic : buf.victims())
		{
			cout << vic << ' ';
		}
		cout << endl;
	}
}

 


빌드는 잘 되지만 실행에서 dll파일과 관련된 에러가 생긴다.

lib은 컴파일할 때 같이 링크돼서 상관없지만 dll은 실행할 때 불러와지는거라서 바이너리파일과 같이 있어야한다고 한다.

그러니 protobuf의 dll 파일들을 프로젝트 바이너리 파일이 있는곳에 복붙해놓자. (debug, release 둘 다)

 

잘 된다!

'강의 수강 > 게임서버(1)' 카테고리의 다른 글

69. 채팅 실습  (0) 2025.08.18
68. 패킷 자동화 #2  (0) 2025.08.18
67. 패킷 자동화 #1  (0) 2025.08.17
54.5 중간 정리  (0) 2025.07.28
게임서버 공부 시작  (0) 2025.03.27

+ Recent posts