Protocol Buffers를 패킷으로 활용해 보자 Programming

Protocol Buffers(이하 PB)를 패킷으로 활용해 보기위한 예제입니다.

패킷으로서 활용하려면 패킷이 뭉쳐서 올수 있으므로  메세지의 길이를 알아낼수 있다거나 끝을 표시해주는 기능이 필요합니다. 하지만 아래 링크의 글을 보시면,
http://code.google.com/apis/protocolbuffers/docs/techniques.html
하나의 스트림에 다수개의 메세지를 쓸때, 메세지의 끝이나 시작을 추적하는 것은 직접 구현해야 합니다. PB에는 메세지의 끝을 알아내는 기능이 없습니다. 이것을 해결하기 위한 가장 쉬운 방법은 메세지를 비트에 쓰기전에 그것의 사이즈를 쓰는것 입니다. 나중에 읽어낼때는 사이즈를 먼저 읽고 그 사이즈 만큼의 메세지의 본체를 읽어내면 됩니다.

헤더를 자체적으로 만들어서 사용하라고 권고합니다. 그래서 아래와 같이 헤더를 먼저 쓰고 그 다음에 메세지를 쓰는 방식으로 구현했습니다.


메세지가 미완성인지 체크해주는 기능은 있으므로 잘려서 오는 것은 걱정없습니다.
bool isCompleted = message.ParseFromCodedStream(&stream);
먼저 simplemessage.proto 파일 입니다.
package simple;

enum MessageType {
    LOGIN = 0;
    CHAT = 1;
    MOVE = 2;
}

message Login {
    required int32 id = 1;
    required string name = 2;
}

message Chat {
    required string name = 1;
    required int32 dst_id = 2;
    required string message = 3;
}

message Move {
    required int32 id = 1;
 
    message Position {
        required float x = 1;
        required float y = 2;
    }
    repeated Position track = 2;
}

이전 포스트 참조해서 h/cc파일을 생성합니다. 아래는 생성된 파일을 이용한 소스 파일 입니다.
#include <string>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <google/protobuf/text_format.h>
#include "simplemessage.pb.h"

using namespace std;
using namespace google;

// 메세지 사이에 끼워넣을 헤더 구조체
struct MessageHeader
{
   protobuf::uint32 size;
  simple::MessageType type;
};
const int MessageHeaderSize = sizeof(MessageHeader);

class PacketHandler
{
public:
   void Handle(const simple::Login& message) const
   {
    PrintMessage(message);
   }
  void Handle(const simple::Chat& message) const
   {
     PrintMessage(message);
   }
  void Handle(const simple::Move& message) const
   {
      PrintMessage(message);
  }

protected:
   // 메세지의 내용을 텍스트 형식으로 출력
  void PrintMessage(const protobuf::Message& message) const
   {
      string textFormatStr;
     protobuf::TextFormat::PrintToString(message, &textFormatStr);
     printf("%s\n", textFormatStr.c_str());
  }
};

// 패킷을 파싱해서 그것을 인자로 적절한 메서드를 호출해줌
void PacketProcess(protobuf::io::CodedInputStream& input_stream, const PacketHandler& handler)
{
  MessageHeader messageHeader;
   // 헤더를 읽어냄
  while( input_stream.ReadRaw(&messageHeader, MessageHeaderSize) )
   {
// 직접 억세스 할수 있는 버퍼 포인터와 남은 길이를 알아냄
    const void* payload_ptr = NULL;
     int remainSize = 0;
    input_stream.GetDirectBufferPointer(&payload_ptr, &remainSize);
    if (remainSize < (signed)messageHeader.size)
       break;

     // 메세지 본체를 읽어내기 위한 스트림을 생성
    protobuf::io::ArrayInputStream payload_array_stream(payload_ptr, messageHeader.size);
     protobuf::io::CodedInputStream payload_input_stream(&payload_array_stream);

     // 메세지 본체 사이즈 만큼 포인터 전진
     input_stream.Skip(messageHeader.size);

// 메세지 종류별로 역직렬화해서 적절한 메서드를 호출해줌
     switch(messageHeader.type)
      {
     case simple::LOGIN:
       {
          simple::Login message;
         if (false == message.ParseFromCodedStream(&payload_input_stream))
             break;
         handler.Handle(message);
        }
      break;
     case simple::CHAT:
        {
          simple::Chat message;
          if (false == message.ParseFromCodedStream(&payload_input_stream))
            break;
          handler.Handle(message);
       }
        break;
     case simple::MOVE:
       {
          simple::Move message;
         if (false == message.ParseFromCodedStream(&payload_input_stream))
            break;
         handler.Handle(message);
       }
        break;
   }
   }
}

// 메세지를 출력용 스트림에 쓴다
void WriteMessageToStream(
simple
::MessageType msgType,
const protobuf::Message& message,
protobuf::io::CodedOutputStream& stream)
{
  MessageHeader messageHeader;
   messageHeader.size = message.ByteSize();
  messageHeader.type = msgType;
   stream.WriteRaw(&messageHeader, sizeof(MessageHeader));
  message.SerializeToCodedStream(&stream);
}

int main(int argc, char* argv[])
{
  // 메세지에 값 세팅
   simple::Login login;
  login.set_name("alice");
   login.set_id(1);

  simple::Chat chat;
   chat.set_name("rabbit");
  chat.set_dst_id(2);
   chat.set_message("How are you doing?");

  simple::Move move;
   move.set_id(3);
  simple::Move::Position* pos0 = move.add_track();
   pos0->set_x(10.01f);
  pos0->set_y(10.02f);
  simple::Move::Position* pos1 = move.add_track();
   pos1->set_x(20.01f);
  pos1->set_y(20.02f);
   simple::Move::Position* pos2 = move.add_track();
  pos2->set_x(30.01f);
   pos2->set_y(30.02f);

  // 필요한 버퍼의 사이즈를 알아내서 버퍼를 할당
   int bufSize = 0;
  bufSize += MessageHeaderSize + login.ByteSize();
   bufSize += MessageHeaderSize + chat.ByteSize();
  bufSize += MessageHeaderSize + move.ByteSize();
   protobuf::uint8* outputBuf = new protobuf::uint8[bufSize];

  // 메세지를 출력할 스트림 생성
   protobuf::io::ArrayOutputStream output_array_stream(outputBuf, bufSize);
  protobuf::io::CodedOutputStream output_coded_stream(&output_array_stream);

   // 메세지를 스트림에 쓴다
  WriteMessageToStream(simple::LOGIN, login, output_coded_stream);
   WriteMessageToStream(simple::CHAT, chat, output_coded_stream);
  WriteMessageToStream(simple::MOVE, move, output_coded_stream);

   // 패킷 핸들러와 입력용 스트림 생성
  PacketHandler handler;
   protobuf::io::ArrayInputStream input_array_stream(outputBuf, bufSize);
  protobuf::io::CodedInputStream input_coded_stream(&input_array_stream);

   // 패킷 분석
  PacketProcess(input_coded_stream, handler);

   // 버퍼 해제
  delete [] outputBuf;
   outputBuf = NULL;
  return 0;
}
실행 결과 입니다.



핑백

덧글

댓글 입력 영역