proto3

민정·2025년 7월 3일

gRPC

목록 보기
3/7

Well-formed Messages

  • 정상적으로 직렬화, 역직렬화가 가능한 메시지
  • 메시지를 바이너리로 만들었을 때, 바이너리 데이터를 메시지 객체로 읽을 때, 프로토콜 버퍼의 규칙에 맞는 올바른 데이터가 됨
  • .proto 파일 자체가 문법적으로 올바른지도 포함

와이어 포맷이란

  • 메시지를 네트워크로 전송하거나 파일에 저장할 때 사용하는 바이너리 인코딩 형식
    • 사람이 읽는 텍스트가 아님
    • 각 필드의 번호와 값, 그리고 타입 정보를 조합해서 만든 컴팩트한 이진 데이터
  • 메시지를 인코딩해 와이어 포맷으로 만들어 송신한 후 수신 측에서 디코딩해 메시지를 수신

메시지 구조

syntax = "proto3";

message SearchRequest {

    optional string query = 1;
    
    int32 page_number = 2;
    
    repeated string filters = 3;
    
    map<string, string> metadata = 4;
    
    optional int32 results_per_page = 5;
}
  • proto3에서는 반드시 syntax 명시
  • 한 파일에 여러 message 포함 가능
    • 다양한 종속성을 가진 많은 message를 하나의 파일에 정의하면 종속성 문제가 발생 가능
    • .proto 파일당 가능한 한 적은 수의 메시지만 포함하는게 좋음

필드

필드 번호 할당

  • 각 필드에 1~536,870,911 사이의 숫자 지정
    • 필드 번호는 해당 메시지에서 고유
  • 가장 자주 설정되는 필드에는 1~15까지의 필드 번호를 사용
    • 필드 번호가 낮을수록 와이어 포맷에서 공간을 덜 차지함
  • 필드 번호 19,00019,999는 프로토콜 버퍼 구현을 위해 예약됨
    • 해당 필드 번호 사용 불가
    • 사용 시 protoc에서 오류 발생
  • 필드 번호는 절대 재사용해서는 안됨
    • 와이어 포맷 메시지는 필드 이름이 아니라 필드 번호로만 필드를 식별
    • 한 필드 번호를 삭제했다가, 다른 필드에 재사용시 잘못 해석될 수 있음

presence

optional

  • 사용 권장 옵션
  • 사용자가 이 필드를 설정했는지 여부를 구분할 수 있음
  • 설정하지 않으면 직렬화 시 포함되지 않고, 디코딩 시 기본값이 반환

implicit(명시 안함)

  • 사용을 권장하지 않음
  • proto3에서 기본적으로 optional을 명시하지 않으면 implicit으로 사용
  • 사용자가 이 필드를 설정했는지 여부를 구분할 수 없음
    • 스칼라 타입에서 권장하지 않음
    • 값이 0일 때, 이게 기본값인지, 실제로 0을 넣은 건지 구분할 수 없기 때문

repeated

  • 여러 개의 값을 가질 수 있고, 값의 순서도 보장
  • 값이 없으면 빈 배열로 반환

map

  • 여러 개의 key-value 쌍을 저장
  • 값이 없으면 빈 맵으로 반환

필드 번호 예약

  • 삭제하려는 필드 번호를 reserved 에 추가해 재사용 방지
    - 예약된 필드 번호를 사용하려고 하면 protoc 컴파일러가 오류 메시지를 생성
message Foo {   
	reserved 2, 15, 9 to 11; 
}

타입

메시지 타입

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}
  • 다른 메시지를 필드 타입으로 사용 가능
message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

message SomeOtherMessage {  
	SearchResponse.Result result = 1; 
}
  • 메시지 내에서 메시지 타입 정의 및 사용 가능
    • 원하는 만큼 메시지를 중첩 가능(깊이 제한 없음)
  • 부모 메시지 외부에서 Parent.Type 으로 참조 가능

스칼라 타입

  • 실수: double, float
  • 정수
    • 가변 길이
      • int32, int64
      • uint32, uint64 → 양수만
      • sint32, sint64 → 음수가 많을 때 적합
    • 고정 길이
      • fixed32, fixed64
      • sfixed32, sfixed64
  • 부울: bool
  • 문자열: string
  • 바이트: bytes

Enum

  • 열거형 기본값은 열거형에 정의된 첫 번째 값(필드 번호가 0)
  • message 안에 정의된 enum도 다른 메시지에서 사용 가능
  • MessageType.EnumType 과 같이 사용
  • 서로 다른 열거형 상수에 동일한 값을 할당하여 별칭을 정의할 수 있음
    •  allow_alias 옵션을 true로 설정
    • 모든 별칭 값은 직렬화에 유효하지만, 역직렬화 시에는 첫 번째 값만 사용
  • Enum도 메시지 필드처럼 값 재사용을 막기 위해 reserved 키워드 사용
enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1;
  EAA_FINISHED = 2;
}

Map

map<key_type, value_type> map_field = N;
  • key_type 
    • 부동 소수점 타입(double, float) 과 bytes 사용 불가
    • 열거형이나 메시지 타입 사용 불가
  • value_type
    • map을 제외한 모든 타입 사용 가능

Any

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}
  • Any는 이름 그대로 아무 메시지나 담을 수 있는 특별한 타입
    • 미리 .proto 파일에 정의되어 있지 않은 메시지도 담을 수 있음
  • Any는 bytes로 직렬화된 임의의 메시지를 포함하며 해당 메시지내에는 전역적으로 고유하게 식별하는 URL 정보도 포함
  • Any 형을 사용하기 위해서는 google/protobuf/any.proto 를 import 해야함

One of

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}
  • 여러 필드 중 동시에 하나만 값이 들어갈 수 있도록 강제하는 기능
    • oneof 필드 내의 모든 필드가 메모리를 공유
    • oneof 필드 내의 멤버를 하나 설정하면 oneof 내의 다른 모든 멤버가 자동으로 삭제
  • map 및 repeated를 제외한 모든 유형의 필드를 oneof에 추가 가능
  • oneof 내 여러 필드의 값을 설정하는 경우 마지막으로 설정한 필드만 값을 유지
    • 위 예제에서 name, sub_message가 둘 다 값을 갖는 경우 마지막에 있는 sub_message 값만 남음

import message

  • 다른 .proto 파일에 정의된 메시지 타입을 필드 타입으로 사용하려면, 해당 .proto 파일을 import해야 함
    • import "myproject/other_protos.proto";
  • .proto 파일의 위치를 변경하면, 기존에 해당 파일을 import하던 모든 코드에서 import 경로를 수정해야 하는 번거로움이 발생
  • import public을 사용하면,
    • 기존 위치에 내용이 없는 .proto 파일 배치
    • 그 파일에서 새 위치의 .proto 파일을 import public로 연결
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
  • import public은 전이성을 가짐
    • B에 중요한 타입, 메시지 정의
    • A가 B를 public import로 가져옴
    • C가 A만 import, C는 B의 정의도 사용 가능

Service

  • .proto 파일에서 RPC 서비스 인터페이스 정의 가능
  • protoc 는 선택한 언어로 서비스 인터페이스 코드 및 stub을 생성
service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}
  • RPC 서비스에서 SearchRequest를 매개변수로 갖고 SearchResponse를 반환하는 메소드 정의 가능
  • service 정의 방법: 4가지 종류의 메소드 정의 가능
rpc GetFeature(Point) returns (Feature) {}

// 서버 측 스트리밍 RPC
// 클라이언트는 더 이상 메시지가 없을때까지 스트림 읽음
rpc ListFeatures(Rectangle) returns (stream Feature) {}

// 클라이언트 측 스트리밍 RPC
// 클라이언트의 일련의 메시지 작성 및 서버 응답 대기 
rpc RecordRoute(stream Point) returns (RouteSummary) {}

// 양방향 스트리밍 RPC
// 클라이언트, 서버가 독립적이기 때문에 원하는 순서대로 읽고 쓰기 가능
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
profile
시스템 + 리눅스 + 클라우드

0개의 댓글