JS/TIL(Today I Learned)

2025-01-23 <프로토 버프과 one of 문법>

프린스 알리 2025. 1. 23.

내일배움캠프 Node.js 트랙 59일차

프로토 버프과 one of 문법

프로토 버프

Protocol Buffers(이하 Protobuf)는 Google에서 개발한 언어 중립적이고, 플랫폼 독립적인 데이터 직렬화 형식이다. 무슨 말이냐하면 Node.js 서버에서 프로토 버프로 직렬화한 패킷을 보내도 C# 클라이언트가 해석할 수 있다는 뜻이다.

프로토 버프의 특징

  1. 효율적이다! : Protobuf는 바이너리 형식으로 데이터를 인코딩하여 JSON이나 XML보다 더 작은 크기와 빠른 처리 속도를 제공한다.
  2. 언어 중립적이다! : 다양한 프로그래밍 언어에서 사용할 수 있어, 여러 플랫폼 간 데이터 교환이 용이하다.
  3. 스키마로 정의한다! : .proto 파일을 통해 데이터 구조를 명시적으로 정의할 수 있다.
  4. 버전 관리가 된다! : 추후에 새로운 필드를 추가하더라도 기존 필드와 충돌하지 않기 때문에, 하위 호환성을 유지하면서 스키마를 쉽게 업데이트할 수 있다.

프로토 버프에서 one of 사용하기

oneof 키워드를 사용하면 여러 필드 중 하나만 값을 가질 수 있도록 설정할 수 있다. 쉽게 말해, oneof는 여러 필드 중 "오직 하나"만 활성화될 수 있도록 보장하는 구조이다. 예를 들어, 서로 상충될 수 있는 데이터를 정의하거나, 상황에 따라 다른 데이터를 저장하고 싶을 때 사용된다.

syntax = "proto3";

message Example {
  oneof data {
    string text = 1;
    int32 number = 2;
    bool flag = 3;
  }
}

주요 특징

정의된 필드 중 하나만 사용이 가능하다.

=> 위 예제에서 text, number, flagoneof 블록 안에 포함되어 있으므로, 동시에 두 개 이상의 필드에 값을 설정할 수 없다.

메모리를 효율적으로 사용한다.

=> 모든 필드가 개별적으로 메모리를 차지하지 않는다.
=> 활성화된 필드만 메모리를 사용하므로, 메모리 사용량이 줄어든다.

유연한 메시지 설계가 가능하다

=> 하나의 메시지 타입에 다양한 경우의 수를 표현할 때 유용하다. 예를 들어, 요청/응답 구조, 다양한 액션 타입을 처리하는 경우 등에 적합하다.

사용 예시

message GamePacket {
    oneof payload {
        C2SRegisterRequest registerRequest = 1;
        S2CRegisterResponse registerResponse = 2;
        C2SLoginRequest loginRequest = 3;
        S2CLoginResponse loginResponse = 4;
    }
}
const gamePacket = new GamePacket();

// 필드 하나만 설정 가능
gamePacket.registerRequest = { username: 'player1', password: '1234' };

// 다른 필드를 설정하면 기존 값은 초기화됨
gamePacket.loginRequest = { username: 'player1', password: '1234' };

// 결과적으로 registerRequest는 초기화됨
console.log(gamePacket.registerRequest); // undefined
console.log(gamePacket.loginRequest);    // { username: 'player1', password: '1234' }

=> 같은 메시지 내에서 논리적으로 서로 배타적인 데이터를 정의할 때 적합하다.
=> 로그인 요청과 회원가입 요청은 동시에 발생하지 않으므로 oneof를 사용해 설계가 가능하다.

팀 프로젝트에서 one of가 사용된 예시

message GamePacket {
    oneof payload {
        // 회원가입 및 로그인
        C2SRegisterRequest registerRequest = 1;
        S2CRegisterResponse registerResponse = 2;
        C2SLoginRequest loginRequest = 3;
        S2CLoginResponse loginResponse = 4;

        // 매칭
        C2SMatchRequest matchRequest = 5;
        S2CMatchStartNotification matchStartNotification = 6;
                // ... 생략 ...

        // 몬스터 사망 통지
        C2SMonsterDeathNotification monsterDeathNotification = 20;
        S2CEnemyMonsterDeathNotification enemyMonsterDeathNotification = 21;
    }
}
message C2SRegisterRequest {
    string id = 1;
    string password = 2;
    string email = 3;
}

message S2CRegisterResponse {
    bool success = 1;
    string message = 2;
    GlobalFailCode failCode = 3;
}

message C2SLoginRequest {
    string id = 1;
    string password = 2;
}

message S2CLoginResponse {
    bool success = 1;
    string message = 2;
    string token = 3;
    GlobalFailCode failCode = 4;
}

 

게임 패킷의 타입을 one of 방식으로 정의하고, 타입 내부에서 하위 프로토버프 스키마를 정의하는 게 가능하다. 서버와 클라이언트가 같은 스키마를 쓰기로 약속한다면 one of에 정의된 필드 중 하나만 활성화 시켜서 패킷을 주고 받을 수 있다는 것!

 

즉, 클라이언트가 서버로 request를 보낼 때 해당 패킷은 저 많고 많은 필드 중 무언가 하나만 활성화된 상태란 뜻이다. 서버는 클라이언트가 보낸 패킷을 GamePacket으로 받아서 역직렬화하며, oneof에서 활성화된 필드만 처리하게 된다.

 

따라서, 우리는 패킷 타입명을 일일이 작성하지 않아도 GamePacket이라는 최상위 명칭으로 파싱 작업이 가능하다!

'JS > TIL(Today I Learned)' 카테고리의 다른 글

2025-01-27 <운영체제 개요>  (0) 2025.01.27
2025-01-24  (1) 2025.01.24
2025-01-22 <더 이상 쓰이지 않는 객체는 어떻게 될까?>  (2) 2025.01.22
2025-01-21  (2) 2025.01.21
2025-01-20  (3) 2025.01.20

댓글