나만의 작은 도서관

[TIL][C++] 250514 MMO 서버 개발 17일차: 빌드 전에 protoc 자동 호출 시키기, .proto 파일 작성 시 유의사항, PacketHandler.h 파일 자동 생성을 위해 템플릿 엔진 jinja2 활용하기 본문

Today I Learn

[TIL][C++] 250514 MMO 서버 개발 17일차: 빌드 전에 protoc 자동 호출 시키기, .proto 파일 작성 시 유의사항, PacketHandler.h 파일 자동 생성을 위해 템플릿 엔진 jinja2 활용하기

pledge24 2025. 5. 14. 23:53
주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다. 

빌드 전에 protoc 자동 호출 시키기

  • protoc.exe에 명령줄을 넣어 실행하면. proto →. cpp로 변환된다(cpp가 아닌 다른 언어도 된다) 그런데, 개발자도 사람인지라. proto를 수정해 놓고. protoc를 실행하지 않는 실수를 범할 수 있다. 이 부분을 해결하기 위해. proto가 수정되면 자동으로 protoc가 실행되었으면 한다.
  • . protoc가 실행되는 시점은 여럿 있겠지만, 정말로 수정된 다음 저장할 때마다 protoc를 실행시키면 실행 빈도가 잦을 것이다(Unity에서 코드 한 줄 수정할 때처럼..) 그러니 시점은 빌드 전에 protoc를 실행하는 것이 가장 적당하다.

빌드 전에 배치 파일을 자동으로 호출하기

  • .proto →. cpp 뿐만 아니라, 각 프로젝트에 xxx.pb.h / xxx.pb.cc를 뿌려주는 작업까지를 배치 파일 하나에서 처리하고 있다. 즉, 배치 파일을 호출하면 원하는 곳에 변환된 파일들이 들어간다.
  • 이제 이 배치 파일을 빌드 전에 자동으로 실행하도록 해야 한다. 방법은 여러 개 있을 수 있지만, MSVC에는 프로젝트 속성에 빌드 전 이벤트를 발생시킬 수 있는 기능이 있다. 이를 활용해서 배치 파일을 실행시킨다. 빌드 전 이벤트에 아래 명령줄을 추가하면 된다.
CALL $(SolutionDir)Common\\Protobuf\\bin\\GenPackets.bat
  • 이제 빌드 전에 자동으로 배치 파일이 실행될 것이다.

.proto파일 수정 관련 문제점

  • 문제는. proto는 MSVC에서 수정 감지 대상이 아니라는 것이다. 그래서. proto만 수정하고 소스 코드를 수정하지 않은 경우에는 빌드가 이뤄지지 않는다(최신 상태로 감지되며 빌드 안 함) 이를 해결하기 위해, MSCV의 프로젝트 파일 중 하나인. vcxproj를 건드려서 수정 감지 대상으로. proto 파일을 등록해야 한다.
  • . vcxproj는 텍스트 파일이므로 텍스트 편집기에선 다 열 수 있다. (nodepad++도 되고, vscode도 된다.) 편집기로 열었다면 수정 감지 대상으로 지정하는 UpToDateChenkInput 속성을. proto 파일에 설정한다. 아래와 같이 설정하면 된다. (해당 프로젝트의 visual studio는 끄고 하는 것이 좋다.)
  <ItemGroup>
    <UpToDateCheckInput Include="..\\Common\\Protobuf\\bin\\Enum.proto" />
    <UpToDateCheckInput Include="..\\Common\\Protobuf\\bin\\Protocol.proto" />
    <UpToDateCheckInput Include="..\\Common\\Protobuf\\bin\\Struct.proto" />
  </ItemGroup>
  • 위와 같이 설정했다면, 등록한 . proto 파일도 수정 감지 대상으로 지정되어 빌드 시 소스 파일이 수정되지 않았더라도 빌드가 진행되게 된다.

.proto 파일 작성 시 유의사항

  1. .proto 파일 분리의 필요성
    • . proto 파일에서는 패킷의 페이로드 구조를 정의할 뿐만 아니라, enum이나 구조체를 정의할 수 있다. 이러한 페이로드 구조 내에서 사용할 다양한 타입들이 많이 정의되고, 같은 파일에서 작성된다면 관리하지 어려워진다. 따라서, 확장성을 위해 용도에 따라 분리하는 것이 좋다.
    • 물론 주석을 이용해서 같은 파일에서 시각적으로 분리할 수도 있을 것이다. 하지만, .proto 파일을 분리하지 않고 관리했을 때 문제가 하나 더 있다.
    // === 구조체 시작 ====
    message BuffData {}
    ...
    // === 구조체 끝 ===
    
    // === 패킷 시작 ===
    message S_TEST {}
    // === 패킷 끝 ===
    
    • 현재 시스템상 .proto 파일의 수정을 감지하고 빌드 전에 연결해 둔 배치 파일이 자동으로 실행된다. 그런데, 여기서 정보를 수정한다면 모든 .proto 파일 내용을 protoc를 통해 처리될 것이다. .proto 파일의 크기가 작으면 상관없겠지만 커질수록 빌드 시간을 증가하게 될 것이다.
    • 따라서, 확장하기 쉬운 구조 + 수정시 빌드 시간 절약의 이유로 .proto 파일을 분리해 두는 것이 좋다.
    syntax = "proto3";
    package Protocol;
    
    // 분리한 .proto 파일 임포트
    import "Enum.proto"; 
    import "Struct.proto";
    
    message S_TEST {}
    
  2. protobuf의 enum 사용 시 주의점
    • protobuf의 enum은 c++의 enum과 달리 반드시 0으로 시작해야 한다고 한다. enum을 사용하게 된다면 NONE과 같은 안쓰는 enum값을 추가해야한다.
    enum PlayerType
    {
    	PLAYER_TYPE_NONE = 0;
    	...
    }
    

PacketHandler.h 파일 자동 생성을 위해 템플릿 엔진 jinja2 활용하기

  • .proto 파일이 변경될 때마다 protobuf 파일의 갱신뿐만 아니라 패킷을 받았을 때 패킷을 처리하는 PacketHandler도 갱신되어야지 비로소 갱신된. proto 파일에 맞게 로직을 실행하는 구조가 완성된다. 그런데, 새로운 패킷이 생길 때마다 새로운 패킷의 packetId와 이에 매핑된 Handle함수를 추가하는 것은 번거로운 작업이 될 수 있다. 또한 실수의 여지도 있어 이 부분은 자동화를 해두는 것이 좋아 보인다.
  • jinja2는 템플릿 엔진으로, 템플릿 엔진이란 미리 정의된 템플릿과 동적 데이터를 결합하여 최종 출력물을 생성하는 소프트웨어 컴포넌트를 의미한다.
    • 즉, 미리 만들어둔 템플릿(일종의 양식)과 동적 데이터(내용)를 넣으면 템플릿에 맞게끔 데이터가 들어간 코드가 완성된다!
[template]+[sourceCode] => [jinja2] => [outputCode]
  • 이 jinja2는 파이썬 패키지로 존재하며, 파이썬을 이용해 미리 만들어둔 PacketHandler 템플릿과. proto에서 파싱 한 패킷 이름을 결합해 PacketHandler.h를 만들 수 있다.
  • 그래서 이 jinja2를 통해 PacketHandler.h를 자동으로 만드는 과정을 빌드 전 이벤트로 넣으면 PacketHandler.h가 자동으로 만들어지게 할 수 있다.