컨텐츠 바로가기

Google Protocol Buffers 기본 사용법

http://javawork.egloos.com/2720889

Protocol Buffers는 구글에서 내놓은 오픈소스 직렬화 라이브러리 입니다. 메세지(혹은 구조체)를 연속된 비트로 만들고, 반대로 비트에서 원래의 메세지로 만들수있습니다. 게임에서는 패킷 전송시에 유용하게 사용할 수 있습니다. C++, java, python을 지원하는데, C++에서 사용하는 방법만을 소개하도록 하겠습니다. 2.3.0 버젼을 기준으로 합니다.

일단 다운로드 받아서 모두 컴파일 합니다.
http://code.google.com/p/protobuf/

메세지를 정의 합니다.
package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}
이것을 .proto 파일로 저장하고 protoc에 인자로 넣어주면 이것을 기반으로 C++코드를 생성해 줍니다.
protoc.exe 파일은 protobuf-2.3.0/vsprojects/Debug 혹은 Release에 들어있습니다.

아래는 배치파일 입니다.
set DST_DIR=D:\Work\ProtoBufExample
set SRC_DIR=D:\Work\ProtoBufExample
protoc -I=%SRC_DIR% --cpp_out=%DST_DIR% %SRC_DIR%/addressbook.proto
이렇게 하면 addressbook.pb.h/cc 파일이 생성됩니다.

다음은 생성된 코드를 이용하여 직렬화, 역직렬화를 수행하는 코드입니다.
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>

using namespace std;
using namespace google;

int main(int argc, char* argv[])
{
// Message 객체에 값 세팅
tutorial::Person src_person;
src_person.set_id(41);
src_person.set_name("alice");
src_person.set_email("alice@anydomain.com");
tutorial::Person::PhoneNumber* phone0 = src_person.add_phone();
phone0->set_number("123-0101");
phone0->set_type(tutorial::Person_PhoneType_MOBILE);
tutorial::Person::PhoneNumber* phone1 = src_person.add_phone();
phone1->set_number("456-0202");
phone1->set_type(tutorial::Person_PhoneType_HOME);

// 미리 생성해야 하는 버퍼의 길이를 알아내어 버퍼할당
int bufSize = src_person.ByteSize();
char* outputBuf = new char[bufSize];

// 버퍼에 직렬화
protobuf::io::ArrayOutputStream os(outputBuf, bufSize);
src_person.SerializeToZeroCopyStream(&os);

// 버퍼에서 역직렬화
protobuf::io::ArrayInputStream is(outputBuf, bufSize);
tutorial::Person dst_person0;
dst_person0.ParseFromZeroCopyStream(&is);

// Message 객체에서 값 가져오기
string name = dst_person0.name();
int id = dst_person0.id();
for (int i=0;i<dst_person0.phone_size();++i)
{
const tutorial::Person_PhoneNumber& phone = dst_person0.phone(i);
tutorial::Person_PhoneType phone_type = phone.type();
string phone_number = phone.number();
}

// 파일에 직렬화
const char* test_filename = "person.txt";
fstream ofs(test_filename, ios::out | ios::trunc | ios::binary);
src_person.SerializeToOstream(&ofs);
ofs.close();

// 파일에서 역직렬화
fstream ifs(test_filename, ios::in | ios::binary);
tutorial::Person dst_person1;
dst_person1.ParseFromIstream(&ifs);
ifs.close();

// 메모리 해제
delete [] outputBuf;
outputBuf = NULL;
protobuf::ShutdownProtobufLibrary();
return 0;
}
문자열의 길이에 따라 메세지의 사이즈가 달라지는 것은 당연하고, int형 변수의 크기에 따라서도 사이즈가 달라집니다. 예를 들면 위의 예제에서 id값이 41이면 총 메세지 사이즈는 58, 65530 이면 60 입니다.

직렬화된 비트에서 text format의 문자열을 추출해 낼수도 있습니다.
#include <google/protobuf/text_format.h>

...

// 텍스트 형식으로 출력
string textFormatStr;
protobuf::TextFormat::PrintToString(src_person, &textFormatStr);
printf("%s\n", textFormatStr.c_str());

// 문자열에서 parsing
tutorial::Person dst_person2;
protobuf::TextFormat::ParseFromString(textFormatStr, &dst_person2);


출력된 text format의 형태입니다.

유사해 보이지만 JSON도 아니고 YAML도 아닌 자체 포맷으로 출력해줍니다.

저는 직렬화된 비트에서 JSON형태로 뽑아내는 기능을 원했는데, 아무래도 직접 구현해야 할 것 같습니다.
아래 개발자 인터뷰를 보니 Reflection을 사용하면 가능할 것 같기도 합니다.
http://news.oreilly.com/2008/07/interview-google-open-sources.html

java예제도 다루고 있는 참고 링크
http://aploit.egloos.com/5233561

덧글|덧글 쓰기|신고