티스토리 뷰

??

protobuf

4whomtbts 2019. 12. 22. 20:48

protobuf 는 Protocol Buffer 의 줄임말입니다. 

어떤 데이터 구조(학생정보, 전화번호부)를 여러 컴퓨터끼리 주고받을 때, 필연적으로 이러한 데이터 구조를 포맷이 있는

바이너리로 저장하여 송신하고, 수신자에는 송신자와 협의된 포멧을 이용해서 다시 원래의 데이터로 구조화를 시켜야합니다. 

그런데, 이런 문제는 듣기에는 serialize하면 끝나는 문제처럼 보이지만 아래와 같은 문제들이 있습니다.

 

1. 메모리에 적재된 데이터 구조는 바이너리 형식으로 전송되고 저장될 수 있지만, 서로 다른 메모리 레이아웃,

엔디안(리틀엔디안, 빅엔디안) 등등의 문제로 매우 fragile하다고 볼 수 있습니다. 또한 확장성에서도 좋지 않습니다

 

2.  XML을 사용하는 경우, XML은 매우 human readable하지만, space intensive하고, encoding과 decoding이 매우 비쌉니다.

 

Protocol buffer는 이러한 문제를 해결해, 유연성 효율성 자동화된 솔루션을 통해 문제해결을 돕습니다. 

proto라는 확장자를 가진 파일에 만들고자 하는 데이터 구조를 작성해서 protobuf compiler를 이용하면 

해당 언어에 맞춘 방식으로 class와 method를 생성해줍니다. 

 

protobuf 를 만들어보겠습니다.

우선, protobuf compiler를 설치해주시고, shell에서 protoc 를 입력해서 정상실행 된다면 설치가 완료된 것 입니다.

윈도우 사용자분들은 별도의 환경변수 설정을 해주어야 할 것 입니다.


#include 
#include "proto-gen/Student.pb.h"

int main() {

    // 링크 될 protobuf 버전이, 컴파일 된 proto 버전과 호환되는지 assert
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    student::Student *new_student = new student::Student();
    new_student->set_studentid(2015112391);
    new_student->set_name("4whomtbts");
    new_student->set_dept("Department of computer");
    new_student->set_major("Computer science");
    new_student->set_gpa("3.0");
    new_student->PhoneType_Name(student::Student_PhoneType_MOBILE);

    student::Student::PhoneNumber* cell_phone = new_student->add_phones();
    cell_phone->set_number("010-1234-5678");
    student::Student::PhoneNumber* lab_phone = new_student->add_phones();
    lab_phone->set_number("02-2260-1234");

    std::cout << new_student->SerializeAsString() << std::endl;
    // 선택적 : libprotobuf 라이브러리로 인해 만들어진 모든 전역 객체를 삭제
    // 필수적이지는 않지만, 대부분의 프로그램에서 필요할 일이 없는 것 이기 때문에
    // 아래의 코드를 호출함으로써, 자원을 save할 수 있습니다.
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}

그리고나서, C++ 프로젝트를 생성하신 후

위의 디렉터리 구조에서, proto와 proto-gen이라는 디렉토리만 따라서 만들어주시고

만드신 proto 디렉토리 안에, Student.proto 라는 파일만 만들어주시고 아래처럼 따라쳐주세요.

위의 코드가 protobuf의 코드입니다.  

여러가지 키워드에 대해 설명하면

required 는 이름에서 알 수 있듯이 반드시 값을 넣어주어야 하는 필드를 의미합니다.

또한, 필드들에 1씩 증가하는 숫자들을 대입해주는 것을 볼 수 있는데 이것은 unique 한 tag라고 보아야 합니다.

이 tag를 바이너리 인코딩 시점에서 사용합니다. 태그 넘버는 바이너리화 됬을 때, 1~15 까지는 한 바이트를 씁니다(0000이 1이고 

1111이 15이므로), 여기서 알 수 있듯이 16이상의 태그넘버를 쓰면 바이너리가 커집니다(무려 1바이트나) protobuf를

사용하는 것은 efficiency를 위함이므로, 태그넘버는 15이하로 한정지어 사용하기를 권장합니다. 16이상의 태그에

걸맞은 데이터는 자주 안 쓰이는 optional한 원소들입니다. 

위의  enum은 프로그래밍 언어에서 일반적으로 사용하는 그 enumeration입니다.  이것의 용례가 

message PhoneNumber에서 여실히 들어나고, PhoneNumber를 통해서 message가 embedded될 수 있음을 알 수 있습니다.

PhoneNumber 에서는 enum을 하나의 자료형 처럼 사용하는 것을 볼 수 있습니다. 

repeated 는 이름 그대로 반복될 수 있는 필드입니다. Phone의 경우에는, 연구실, 개인전화, 집전화가 있을 수 있으므로

여러개의 같은 타입을 허용한 것 입니다. 일종의 배열이라고 보시면 되겠습니다.

이제 이거 가지고 뭐하자는건지 의문이 들텐데, 이제 proto compiler가 일을 할 땝니다.

제가 이번에 사용할 protoc 명령어와 옵션은 아래와 같습니다.

protoc -I=기준디렉터리 --cpp_out=목적디렉토리  기준디렉터리/*.proto

proto directory로 shell을 이동시켜주신 후, 

protoc -I=. --cpp_out=../proto-gen ./*.proto 

위의 명령어를 해석하면, 현재 디렉토리를 기준으로, C++코드 형태로 protobuf 를 컴파일 할 것 이고 그 결과물은

현재 디렉토리의 부모 디렉토리에서 proto-gen이라는 디렉토리이고, 현재 폴더에서 proto 확장자를 가진 모든 


#include 
#include "proto-gen/Student.pb.h"

int main() {

    // 링크 될 protobuf 버전이, 컴파일 된 proto 버전과 호환되는지 assert
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    student::Student *new_student = new student::Student();
    new_student->set_studentid(2015112391);
    new_student->set_name("4whomtbts");
    new_student->set_dept("Department of computer");
    new_student->set_major("Computer science");
    new_student->set_gpa("3.0");
    new_student->PhoneType_Name(student::Student_PhoneType_MOBILE);

    student::Student::PhoneNumber* cell_phone = new_student->add_phones();
    cell_phone->set_number("010-1234-5678");
    student::Student::PhoneNumber* lab_phone = new_student->add_phones();
    lab_phone->set_number("02-2260-1234");

    std::cout << new_student->SerializeAsString() << std::endl;
    // 선택적 : libprotobuf 라이브러리로 인해 만들어진 모든 전역 객체를 삭제
    // 필수적이지는 않지만, 대부분의 프로그램에서 필요할 일이 없는 것 이기 때문에
    // 아래의 코드를 호출함으로써, 자원을 save할 수 있습니다.
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}
protobuf 를 컴파일해서 만들어라.

그러면 위와같이 Student.pb.cc , Student.pb.h 를 만들어줍니다(Raft가 붙은 파일은 신경쓰지 않으셔도 됩니다)

Student.pb.cc 를 확인해보시면 무려 309 라인이나 되는(protoc 버전에 따라 라인수는 유동적) C++코드가 생겼습니다.

이것은, 어떻게 Student라는 proto 메세지를 인코딩과 디코딩을 알아서 해주는 똑똑한 코드입니다.

#include <iostream>
#include "proto-gen/Student.pb.h"

int main() {

    // 링크 될 protobuf 버전이, 컴파일 된 proto 버전과 호환되는지 assert
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    student::Student *new_student = new student::Student();
    new_student->set_studentid(2015112391);
    new_student->set_name("4whomtbts");
    new_student->set_dept("Department of computer");
    new_student->set_major("Computer science");
    new_student->set_gpa("3.0");
    new_student->PhoneType_Name(student::Student_PhoneType_MOBILE);

    student::Student::PhoneNumber* cell_phone = new_student->add_phones();
    cell_phone->set_number("010-1234-5678");
    student::Student::PhoneNumber* lab_phone = new_student->add_phones();
    lab_phone->set_number("02-2260-1234");

    std::cout << new_student->SerializeAsString() << std::endl;
    // 선택적 : libprotobuf 라이브러리로 인해 만들어진 모든 전역 객체를 삭제
    // 필수적이지는 않지만, 대부분의 프로그램에서 필요할 일이 없는 것 이기 때문에
    // 아래의 코드를 호출함으로써, 자원을 save할 수 있습니다.
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}


#include 
#include "proto-gen/Student.pb.h"

int main() {

    // 링크 될 protobuf 버전이, 컴파일 된 proto 버전과 호환되는지 assert
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    student::Student *new_student = new student::Student();
    new_student->set_studentid(2015112391);
    new_student->set_name("4whomtbts");
    new_student->set_dept("Department of computer");
    new_student->set_major("Computer science");
    new_student->set_gpa("3.0");
    new_student->PhoneType_Name(student::Student_PhoneType_MOBILE);

    student::Student::PhoneNumber* cell_phone = new_student->add_phones();
    cell_phone->set_number("010-1234-5678");
    student::Student::PhoneNumber* lab_phone = new_student->add_phones();
    lab_phone->set_number("02-2260-1234");

    std::cout << new_student->SerializeAsString() << std::endl;
    // 선택적 : libprotobuf 라이브러리로 인해 만들어진 모든 전역 객체를 삭제
    // 필수적이지는 않지만, 대부분의 프로그램에서 필요할 일이 없는 것 이기 때문에
    // 아래의 코드를 호출함으로써, 자원을 save할 수 있습니다.
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}

위와같이 코드를 작성하고, 아래와 같이 CMake 를 생성해주세요

cmake_minimum_required(VERSION 3.15)
project(libRaft)
SET(CMAKE_CXX_FLAGS "-g -Wall -Werror -std=c++11")

find_package(Protobuf REQUIRED)

if(PROTOBUF_FOUND)
    message ("PROTOBUF IS FOUND")
endif()

PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS proto/Student.proto)

MESSAGE ("PROTO_SRCS = ${PROTO_SRCS}")
MESSAGE ("PROTO_HDRS = ${PROTO_HDRS}")

ADD_EXECUTABLE(libraft main.cpp ${PROTO_SRCS} ${PROTO_HDRS})

TARGET_INCLUDE_DIRECTORIES(libraft
        PUBLIC
        ${PROTOBUF_INCLUDE_DIRS}
        ${CMAKE_CURRENT_BINARY_DIR})

TARGET_LINK_LIBRARIES(libraft
        PUBLIC
        ${PROTOBUF_LIBRARIES})

그리고 빌드해주세요

잘 빌드되어서 실행되었음을 볼 수 있습니다.

이제 위의 코드에 이것저것 추가해서 기능을 확인해보겠습니다.

 

#include <ios>
#include <iostream>
#include <fstream>
#include <string>
#include "proto-gen/Student.pb.h"

int main() {

    // 링크 될 protobuf 버전이, 컴파일 된 proto 버전과 호환되는지 assert
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    student::Student *new_student = new student::Student();
    new_student->set_studentid(2015112391);
    new_student->set_name("4whomtbts");
    new_student->set_dept("Department of computer");
    new_student->set_major("Computer science");
    new_student->set_gpa("3.0");
    new_student->PhoneType_Name(student::Student_PhoneType_MOBILE);

    student::Student::PhoneNumber* cell_phone = new_student->add_phones();
    cell_phone->set_number("010-1234-5678");
    student::Student::PhoneNumber* lab_phone = new_student->add_phones();
    lab_phone->set_number("02-2260-1234");

    std::cout << new_student->SerializeAsString() << std::endl;
    // 선택적 : libprotobuf 라이브러리로 인해 만들어진 모든 전역 객체를 삭제
    // 필수적이지는 않지만, 대부분의 프로그램에서 필요할 일이 없는 것 이기 때문에
    // 아래의 코드를 호출함으로써, 자원을 save할 수 있습니다.
    std::cout << "사용되는 메모리 : " << new_student->SpaceUsedLong() << std::endl;

    std::fstream output("hello.raftlog", std::ios::out | std::ios::binary | std::ios::trunc);
    std::ifstream input("hello.raftlog");
    if(!new_student->SerializeToOstream(&output)) {
        std::cout << "Failed to write address book"<<std::endl;
        return -1;
    }
     output.close();

    std::cout << "스트링 변환 : " << new_student->SerializeAsString() << std::endl;

    student::Student deserialized_student;
    deserialized_student.ParseFromIstream(&input);
    std::cout << "파일에서 읽은 proto message : " << deserialized_student.SerializeAsString() << std::endl;
    input.close();


    return 0;
}

 

'??' 카테고리의 다른 글

Bowtie  (0) 2020.06.13
gdb 유용한 명령어 정리  (0) 2019.05.04
댓글