반응형

 

 

protoc-23.3-win64.zip 다운

protoc : 프로토 버프 컴파일러

 

프로토 파일을 C++이나 C#이나 온갖 언어에 맞는 클래스로 만들어 줌.

 


 

 

Protocol.proto 테스트용 파일 생성.


 

 

기존에 XML로 작성했던 패킷 구조를 프로토 버프로 작성.

프로토 파일이 변환 되면서 나중에 클래스나 구조체로 변경 됨.

이제 컴파일러를 통해 생성한 프로토 파일을 C++로 변환시켜주면 된다.


 

 

 

Protobuf 사이트에 있는 명령어를 복사해와서 배치 파일 생성.

 

 


배치 파일 실행 하면 아래와 같은 오류가 발생 하는데 이는, protobuf에는 uint32까지만 있기 때문이다.

 

https://protobuf.dev/programming-guides/proto3/#scalar

 

Language Guide (proto 3)

This topic covers how to use the version 3 of Protocol Buffers in your project. It contains language-agnostic content. For information specific to the language you're using, see the corresponding documentation for your language.

protobuf.dev

 

 

그러니 uint32로 수정해주자.

 

 

수정 후 배치 파일을 실행하면 아래와 같이 C++파일이 자동 생성된다.

 


 

 

서버와 클라이언트 프로젝트에 프로토 파일 추가.

(나중에는 배치 파일을 수정해서 알아서 우리 프로젝트에 추가되도락 할 것.)

 

 

 

 

아직까지는 빌드가 되지 않는다.

왜냐하면 내부적으로 프로토 버프를 이용하기 위해 사용되는 라이브러리를 다운받지 않았기 때문.

 


 

 

사용하는 프로토버프의 버전에 해당하는 Source code.zip 파일을 받아서 압축해제 해주자.

 

 

빌드하기 위해선느 CMake라는 빌드 관리 프로그램이 필요하다.

 

 

 

 

 

처음 실행하면 에러 발생.

 

2개만 체크하고 나머진 모드 체크 해제.

 

 

그러면 라이브러리 파일들과 솔루션 파일이 생성된다.

 


 

빌드한 프로토 버프의 디버그 파일과 릴리스 파일을 프로젝트 폴더로 옮겨주자.

 

 

마지막으로 pch.h 까지 수정하면 끝.

 

 


 

Protobuf 예제


ClientPacketHandler.h .cpp

 

#pragma once

enum
{
	//클라가 서버한테 보내는 경우 =  C_XXXX
	//서버가 클라한테 보내는 경우 = S_XXXX
	//컨벤셔널을 정하고 사용한다.
	S_TEST = 1
};

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

 

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

	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 << "BUFSIZE : " << 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;
	}
}

 

 

 

 


ServerPacketHandler.h .cpp

 

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

 

#include "pch.h"
#include "ServerPacketHandler.h"


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

	PacketHeader header;
	br.Peek(&header);

	switch (header.id)
	{
		//지금은 서버쪽에서 딱히 핸들링 할 게 없다.
		//클라에서 서버로 보내는 패킷을 설계한적 없기 때문.
	default:
		break;
	}
}

//이렇게 패킷이 생길 때 마다 만들어 주기 보다는 자동화 시키는게 좋다. 
SendBufferRef ServerPacketHandler::MakeSendBuffer(Protocol::S_TEST& pkt)
{
	return _MakeSendBuffer(pkt, S_TEST);
}

 

 

 


IocpServer.cpp

 

	while (true)
	{
		Protocol::S_TEST PKT;
		PKT.set_id(1000);
		PKT.set_hp(100);
		PKT.set_attack(10);

		{
			Protocol::BuffData* data = PKT.add_buffs();
			data->set_buffid(40);
			data->set_remaintime(1.2f);
			data->add_victims(4000);
		}
		{
			Protocol::BuffData* data = PKT.add_buffs();
			data->set_buffid(250);
			data->set_remaintime(5.2f);
			data->add_victims(1000);
			data->add_victims(2000);
		}
		//이렇게 임시객체를 만들어서 사용해도 되지만 꼭 임시객체로 사용할 필요는 없다.
		//멤버변수로 사용해도 좋다.

		//그리고 일일이 add_buffs()로 할당받아서 추가 할 필요 없이 아래와 같이도 사용해서 iterator 타입도 넣어 줄 수 있다.
		//auto m = PKT.mutable_buffs();

		//이제 데이터를 담은 객체가 완성되었으니, 이걸 버퍼에다 넣어줘야 한다.

		SendBufferRef sendBuffer = ServerPacketHandler::MakeSendBuffer(PKT);

		//mmorpg에서 자주 일어나는 패턴
		GSessionManager.Broadcast(sendBuffer);
		this_thread::sleep_for(250ms);

 


 

 

 

예제 코드를 실행하면 위와 같은 경고창이 나온다.

 

 

 

왜냐하면 우리가 옮겨 놓은 lib 파일 같은 경우 스태틱 라이브러리이기 때문에

코드를 빌드할 때 바이너리에 포함되서  lib안에 있는 내용이 실행파일에 묶여서 같이 나가게 된다.

 

하지만 dll 같은 경우에는 그게 아니라 실행 파일이 시작되는 위치에 있어야 한다.

 

 

즉, 이렇게 실행 파일과 같이 있어야 한다.

반응형

'네트워크 프로그래밍' 카테고리의 다른 글

Job Queue (1)  (0) 2023.10.03
에코 채팅 프로그램  (0) 2023.10.02
PacketSession  (0) 2023.04.15
SendBuffer Pooling  (0) 2023.04.07
SendBuffer  (0) 2023.03.24