이부분은 인증서버의 것과 90% 똑같을 테니 일단 인증서버에서 배껴온다. (10%는 sh파일 -> bat파일)

 

8. PacketHandler

저번엔 PacketHeader 구조체와 PacketSession, CAuthSession 클래스를 만들어봤다.이번엔 CAuthSession의 OnReadPacket 안에서 readbuffer -> 패킷 클래스 객체 를 해줄 함수들을 만들어보자.ClientPacketHandler 클라이언트

dodontak.tistory.com

 

 

14. 패킷 자동화 (1)

지난번에 인증서버 완성을 위해 앞으로 해야될것들을 정리해봤었다.RedisConnectionPoolRedis를 활용한 세션토큰 발급과 활용방법공부 필요.SQL쿼리 바인더현재는 sql인젝션에 취약한 상태임비밀번호

dodontak.tistory.com

 

 

15. 패킷 자동화 (2)

지난번에 proto파일을 파싱해서 ClientPacketHandler.h 을 만드는 파이썬 프로그램을 만들었다.자동화를해서 편하긴 한데 여전히 쉘스크립트를 직접 실행시켜줘야하는 번거로움이 있다.그러니 이번엔

dodontak.tistory.com


 

원하는 자동화 동작은 이렇다.

  1. proto 파일들을 protoc를 사용해 ~~.pb.cc, ~~.pb.h 파일로 변환, DummyClient, GameServer 프로젝트로 이동.
  2. Protocol.proto 파일과 PacketHandler 템플릿을 파이썬 프로그그램을 사용해 ClientPacketHandler, ServerPackerHandler로 변환, DummyClient, GameServer 프로젝트로 이동.

디렉토리 구조

Project/
├── ...
├── DummyClient
│   └── ...
├── GameServer
│   └── ...
├── Protocol
│   ├── GenPackets.exe (작업2의 파이썬 프로그램 복사본)
│   ├── MakePackets.bat (작업1, 2를 수행하는 배치파일)
│   ├── Protocol.proto
│   ├── Struct.proto
│   ├── Enum.proto
│   └── Templates
│       └── PacketHeader.h
└── Tools
    └── PacketGenerator
        ├── GenPacketMaker.bat (py 파일로 작업2의 파이썬프로그램 만드는 배치파일)
        ├── PacketGenerator.py
        ├── ProtoParser.py
        └── GenPackets.exe (작업2의 파이썬 프로그램 원본)

 

VisualStudio 에서 

솔루션에 디렉토리 구조와 똑같이 폴더를 추가해서 "기존항목 추가" 로 파일들을 넣어줬다. (왜 폴더째로 추가는 못할까)

 

 


GenPackets.exe 만들기

템플릿 PacketHandler + Protocol.proto로 헤더파일 생성해주는 프로그램인데, 직접 만들어야한다.

pyinstaller 설치

py -m pip install pyinstaller

py 파일로 파이썬프로그램을 만들어주는 툴이다. 파이썬 없으면 파이썬 설치부터 해야함.

 

PacketGenerator.py

jinja2 라는 파이썬 템플릿 엔진을 사용해 템플릿 PacketHandler를 헤더파일로 바꿔준다.

import argparse
import jinja2
import ProtoParser

def main():

    arg_parser = argparse.ArgumentParser(description = 'PacketGenerator')
    arg_parser.add_argument('--path', type=str, default='/home/server/Protocol', help='proto path')
    arg_parser.add_argument('--output', type=str, default='TestPacketHandler', help='output file')
    arg_parser.add_argument('--recv', type=str, default='C_', help='recv convention')
    arg_parser.add_argument('--send', type=str, default='S_', help='send convention')
    args = arg_parser.parse_args()

    parser = ProtoParser.ProtoParser(1000, args.recv, args.send)
    parser.parse_proto(args.path)

    file_loader = jinja2.FileSystemLoader('Templates', encoding='cp949')
    env = jinja2.Environment(loader=file_loader)
    
    template = env.get_template('PacketHandler.h')
    output = template.render(parser=parser, output=args.output)
    
    f = open(args.output+'.h', "w+")
    f.write(output)
    f.close()

    return

if __name__ == '__main__':
    main()

 

ProtoParser.py

class ProtoParser(object):
    def __init__(self, start_id, recv_prefix, send_prefix):
        self.recv_pkt = [] # 수신 패킷 목록
        self.send_pkt = [] # 송신 패킷 목록
        self.total_pkt = [] # 모든 패킷 목록
        self.start_id = start_id
        self.id = start_id
        self.recv_prefix = recv_prefix
        self.send_prefix = send_prefix

    def parse_proto(self, path):
        f = open(path, 'r')
        lines = f.readlines()

        for line in lines:
            if line.startswith('message') == False:
                continue

            pkt_name = line.split()[1].upper()
            if pkt_name.startswith(self.recv_prefix):
                self.recv_pkt.append(Packet(pkt_name, self.id))
            elif pkt_name.startswith(self.send_prefix):
                self.send_pkt.append(Packet(pkt_name, self.id))
            else:
                continue
            self.total_pkt.append(Packet(pkt_name, self.id))
            self.id += 1
        f.close()



class Packet:
    def __init__(self, name, id):
        self.name = name
        self.id = id

 

GenPacketMaker.bat

파이썬 프로그램을 자동으로 만들어주고, 찌꺼기파일들 삭제하는 배치파일

pushd %~dp0
py -m PyInstaller --onefile PacketGenerator.py
MOVE .\dist\PacketGenerator.exe .\GenPackets.exe
@RD /S /Q .\build
@RD /S /Q .\dist
DEL /S /F /Q .\PacketGenerator.spec
PAUSE

 

py파일들과 bat 파일을 한 폴더에 넣고 실행시키면 GenPackets.exe 파일이 생성된다.


작업

  1. proto 파일들을 protoc를 사용해 ~~.pb.cc, ~~.pb.h 파일로 변환, DummyClient, GameServer 프로젝트로 이동.
  2. Protocol.proto 파일과 PacketHandler 템플릿을 파이썬 프로그그램을 사용해 ClientPacketHandler, ServerPackerHandler로 변환, DummyClient, GameServer 프로젝트로 이동.

작업1, 2를 수행해줄 배치파일을 만들기

MakePackets.bat

pushd %~dp0

protoc -I=./ --cpp_out=./ ./Enum.proto
protoc -I=./ --cpp_out=./ ./Struct.proto
protoc -I=./ --cpp_out=./ ./Protocol.proto

GenPackets.exe --path=./Protocol.proto --output=ClientPacketHandler --recv=C_ --send=S_
GenPackets.exe --path=./Protocol.proto --output=ServerPacketHandler --recv=S_ --send=C_

IF ERRORLEVEL 1 PAUSE

XCOPY /Y Enum.pb.h "..\GameServer\"
XCOPY /Y Enum.pb.cc "..\GameServer\"
XCOPY /Y Struct.pb.h "..\GameServer\"
XCOPY /Y Struct.pb.cc "..\GameServer\"
XCOPY /Y Protocol.pb.h "..\GameServer\"
XCOPY /Y Protocol.pb.cc "..\GameServer\"
XCOPY /Y ClientPacketHandler.h "..\GameServer\"

XCOPY /Y Enum.pb.h "..\DummyClient\"
XCOPY /Y Enum.pb.cc "..\DummyClient\"
XCOPY /Y Struct.pb.h "..\DummyClient\"
XCOPY /Y Struct.pb.cc "..\DummyClient\"
XCOPY /Y Protocol.pb.h "..\DummyClient\"
XCOPY /Y Protocol.pb.cc "..\DummyClient\"
XCOPY /Y ServerPacketHandler.h "..\DummyClient\"

DEL /Q /F *.pb.h
DEL /Q /F *.pb.cc
DEL /Q /F *.h

PAUSE
  1. protoc로  pb.cc, pb.h 파일 만들기
  2. GenPackets.exe로 ClientPacketHandler.h, ServerPackerHandler.h 만들기
  3. GameServer디렉토리와 DummyClient 디렉토리로 복사하기
  4. 복사 완료후에 생성한 파일들 삭제하기

Enum.proto

syntax = "proto3";
package Protocol;

enum PlayerType
{
	PLAYER_TYPE_NONE = 0;
	PLAYER_TYPE_KNIGHT = 1;
	PLAYER_TYPE_MAGE = 2;
	PLAYER_TYPE_ARCHER = 3;
}

Struct.proto

syntax = "proto3";
package Protocol;

import "Enum.proto";

message Player
{
	uint64 id = 1;
	string name = 2;
	PlayerType playerType = 3;
}

Protocol.proto

syntax = "proto3";
package Protocol;

import "Enum.proto";
import "Struct.proto";

message C_CHAT {
  int32 user_id = 1;
  string msg = 2;
}

message S_CHAT {
  int32 user_id = 1;
  string msg = 2;
}

 

PacketHandler.h

authserver에 있는걸 그대로 가져왔는데 컴파일이 안돼서 약간 수정해야했다.

수정사항

  • include WriteBuffer.h -> include SendBuffer.h
    모든 WriteBufferRef -> SendBufferRef
    변수이름 SendBuffer가 클래스이름과 겹쳐서 sendBuffer로 변경
#pragma once

#include "Types.h"
#include "Session.h"
#include "Protocol.pb.h"
#include "SendBuffer.h"
#include <memory>
#include <functional>

extern function<bool(function<void()>&, PacketSessionRef&, BYTE*, int32)> GPacketHandler[UINT16_MAX];

enum : uint16
{
{%- for pkt in parser.total_pkt %}
	PKT_{{pkt.name}} = {{pkt.id}},
{%- endfor %}
};

bool	Handle_INVALID(function<void()>& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len);
{%- for pkt in parser.recv_pkt %}
void	Handle_{{pkt.name}}(PacketSessionRef& session, const Protocol::{{pkt.name}}& pkt);
{%- endfor %}

class { { output } }
{
public:
	static void	Init()
	{
		for (int i = 0; i < UINT16_MAX; ++i)
			GPacketHandler[i] = Handle_INVALID;
{%- for pkt in parser.recv_pkt %}
		GPacketHandler[PKT_{{pkt.name}}] = [](function<void()>& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len) {
			return GetCallback<Protocol::{{pkt.name}}>(outFunc, Handle_{{pkt.name}}, session, buffer, len);
		};
{%- endfor %}
	}

	static bool	PacketHandler(function<void()>& outFunc, PacketSessionRef session, BYTE* buffer, int32 len)
	{
		PacketHeader*	header = reinterpret_cast<PacketHeader*>(buffer);
		return GPacketHandler[header->id](outFunc, session, buffer, len);
	}

{%- for pkt in parser.send_pkt %}
	static SendBufferRef MakeSendBuffer(Protocol::{{pkt.name}}& pkt) { return MakeSendBuffer(pkt, PKT_{{pkt.name}}); }
{%- endfor %}

private:
	template<typename PacketType, typename ProcessFunc>
	static bool	GetCallback(function<void()>& outFunc, ProcessFunc func, PacketSessionRef session, BYTE* buffer, int32 len)
	{
		PacketType	pkt;
		if (false == pkt.ParseFromArray(buffer + sizeof(PacketHeader), len - sizeof(PacketHeader)))
			return false;
		outFunc = [func, session, pkt](){ func(session, pkt); };
		return true;
	}

	template<typename T>
	static SendBufferRef	MakeSendBuffer(T& pkt, uint16 pktId)
	{
		int	headerSize = sizeof(PacketHeader);
		int	pktSize = pkt.ByteSizeLong();
		SendBufferRef	sendBuffer = make_shared<SendBuffer>(headerSize + pktSize);

		PacketHeader	header;
		header.id = pktId;
		header.size = headerSize + pktSize;

		sendBuffer->AppendBuffer(reinterpret_cast<BYTE*>(&header), headerSize);
		pkt.SerializeToArray(sendBuffer->GetCopyBuffer(), pktSize);
		return sendBuffer;
	}
};

 

cpp파일들은 직접 만들어줘야한다.

GameServer \ ClientPacketHandler.cpp

#include "pch.h"
#include "ClientPacketHandler.h"

function<bool(function<void()>&, PacketSessionRef&, BYTE*, int32)> GPacketHandler[UINT16_MAX];

bool	Handle_INVALID(std::function<void()>& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len)
{
	return false;
}

void	Handle_C_CHAT(PacketSessionRef& session, const Protocol::C_CHAT& pkt)
{

}

 

DummyClient \ ServerPacketHandler.cpp

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

function<bool(function<void()>&, PacketSessionRef&, BYTE*, int32)> GPacketHandler[UINT16_MAX];

bool	Handle_INVALID(function<void()>& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len)
{
	return false;
}

void	Handle_S_CHAT(PacketSessionRef& session, const Protocol::S_CHAT& pkt)
{

}

 

Project\Protocol 에 위와같이 준비하고, GenPacket.bat 을 실행하면 파일들이 생성되고, GameServer와 DummyClient로 복사된다.


짜잘한 문제 해결

컴파일을 하는데 Protobuf에서 워닝 굉장히 많이 뜨는 문제

프로젝트 속성 - c/c++ - 고급 - 특정 경고 사용 안함에 워닝코드를 추가하면 해당 워닝을 더이상 안 볼 수 있다.

나는 내 관리 밖인 Protobuf 라이브러리에서 뜨는 워닝이랑 왜뜨는지 모르겠는 유니코드 관련 워닝을 안뜨게했다.

#pragma 로 특정 워닝이 안뜨게 할 수 있는데 워닝이 발생하는 위치가 자동생성된 pb.h, pb.cc 에서 발생하는지라 프로젝트 설정에서 해줘야했다.

 

컴파일 속도가 굉장히 느린 문제

프로젝트 속성 - c/c++ - 명령줄 에서 /MP를 추가하면 컴파일을 멀티코어를 활용해서 해준다고 한다.

이렇게 하니까 30초나 걸리던 컴파일시간이 17초로 좀 줄었다.


현재까지의 git 버전

 

Fix: Template 문제 해결 · Dodontak/Project_Island_GameServer@1f467d1

PacketHandler.h - class가 ClientPacketHandler로 고정되어있던 문제 해결

github.com

이번엔 컴파일까지만 해봤고, 다음에는 자동화코드로 생성된 코드로 기존의 채팅서버 코드를 대체해보자.

'프로젝트 > Project_Island' 카테고리의 다른 글

38. TLS 적용하기  (0) 2026.04.05
37. PacketHandler 테스트  (0) 2026.04.04
35. Protobuf 추가하기  (0) 2026.04.02
34. PacketHeader, PacketSession 추가  (0) 2026.04.02
33. 서버가 send를 너무 많이 하는 문제 해결  (0) 2026.04.02

+ Recent posts