이 번 챕터에는 Packet Generator와 Performance Monitor라는 두 섹션을 살펴 볼 수 있었다. 여기서 성능 모니터의 경우에는 그라파나(Grafana) 같은 웹을 기반으로 한 솔루션이 유행 중이라 현업에 활용하기는 어려울 것으로 보인다.
그래서, 나는 Packet Generator만 조금 살펴 보았는데, 이 것 역시 요새 유행하는 프로토콜 버퍼(protobuf)나 플랫 버퍼(flatbuf) 같이 IDL(Interface Description Language)을 작성하고, 자동으로 build/extract 기능을 타겟 언어 코드로 만들어 주는 기능에 대한 내용이었다.
본 챕터의 코드는 C++로 생성하는 기능만 있으나, 아직 매시브한 게임서버는 C++로 대부분 이루어지기 때문에 내재화된 솔루션을 갖추기 위해 참고할만 하겠다.
책의 예제 IDL을 한번 살펴 보겠다.
IDL의 형태는 대략 이렇다. 사실은 실제 C++ 헤더 파일인데, 이렇게 하는 이유는 메시지의 ID를 위 enum 값을 그대로 이용하기 위해서이다. 저자의 걸출함이 보이는 부분은 PT_VERSION인데, enum 위에 저걸 달아줌으로 해서, PT_VERSION을 기준으로 message ID가 결정되는 것이다. PT_VERSION에 다른 값이 들어가면 ID가 자동으로 변경되므로, 버전 관리가 용이하게 된다.
enum 값 아래에는 /* */으로 된 meta programming 적인 내용이 보인다. 저자께서 제작한 Packet Generator는 바로 저 부분을 parsing하여 builder/extractor를 생성한다. 코드를 간략히 살펴 보았을 때는, 렉서(lexer) 수준의 파싱은 아니지만, 규칙만 잘 맞추면 무리 없이 진행이 될 것으로 보인다.
위 IDL을 통해 생성된 메시지 규약은 대략 아래와 같을 것이다.
접기
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_FLATBUFSTUDY_PT_VERSION_1000000_H_
#define FLATBUFFERS_GENERATED_FLATBUFSTUDY_PT_VERSION_1000000_H_
#include "flatbuffers/flatbuffers.h"
namespace PT_VERSION_1000000 {
struct Message;
struct PT_CHANNEL_NICKNAME;
struct PT_CHANNEL_NICKNAME_SUCC_U;
struct PT_CHANNEL_NICKNAME_FAIL_U;
enum MESSAGE_ID {
MESSAGE_ID_NONE = 0,
MESSAGE_ID_PT_CHANNEL_NICKNAME = 1,
MESSAGE_ID_PT_CHANNEL_NICKNAME_SUCC_U = 2,
MESSAGE_ID_PT_CHANNEL_NICKNAME_FAIL_U = 3,
MESSAGE_ID_MIN = MESSAGE_ID_NONE,
MESSAGE_ID_MAX = MESSAGE_ID_PT_CHANNEL_NICKNAME_FAIL_U
};
inline const MESSAGE_ID (&EnumValuesMESSAGE_ID())[4] {
static const MESSAGE_ID values[] = {
MESSAGE_ID_NONE,
MESSAGE_ID_PT_CHANNEL_NICKNAME,
MESSAGE_ID_PT_CHANNEL_NICKNAME_SUCC_U,
MESSAGE_ID_PT_CHANNEL_NICKNAME_FAIL_U
};
return values;
}
inline const char * const *EnumNamesMESSAGE_ID() {
static const char * const names[] = {
"NONE",
"PT_CHANNEL_NICKNAME",
"PT_CHANNEL_NICKNAME_SUCC_U",
"PT_CHANNEL_NICKNAME_FAIL_U",
nullptr
};
return names;
}
inline const char *EnumNameMESSAGE_ID(MESSAGE_ID e) {
const size_t index = static_cast<int>(e);
return EnumNamesMESSAGE_ID()[index];
}
template<typename T> struct MESSAGE_IDTraits {
static const MESSAGE_ID enum_value = MESSAGE_ID_NONE;
};
template<> struct MESSAGE_IDTraits<PT_CHANNEL_NICKNAME> {
static const MESSAGE_ID enum_value = MESSAGE_ID_PT_CHANNEL_NICKNAME;
};
template<> struct MESSAGE_IDTraits<PT_CHANNEL_NICKNAME_SUCC_U> {
static const MESSAGE_ID enum_value = MESSAGE_ID_PT_CHANNEL_NICKNAME_SUCC_U;
};
template<> struct MESSAGE_IDTraits<PT_CHANNEL_NICKNAME_FAIL_U> {
static const MESSAGE_ID enum_value = MESSAGE_ID_PT_CHANNEL_NICKNAME_FAIL_U;
};
bool VerifyMESSAGE_ID(flatbuffers::Verifier &verifier, const void *obj, MESSAGE_ID type);
bool VerifyMESSAGE_IDVector(flatbuffers::Verifier &verifier, const flatbuffers::Vector<flatbuffers::Offset<void>> *values, const flatbuffers::Vector<uint8_t> *types);
struct Message FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
enum {
VT_PACKET_TYPE = 4,
VT_PACKET = 6
};
MESSAGE_ID Packet_type() const {
return static_cast<MESSAGE_ID>(GetField<uint8_t>(VT_PACKET_TYPE, 0));
}
const void *Packet() const {
return GetPointer<const void *>(VT_PACKET);
}
template<typename T> const T *Packet_as() const;
const PT_CHANNEL_NICKNAME *Packet_as_PT_CHANNEL_NICKNAME() const {
return Packet_type() == MESSAGE_ID_PT_CHANNEL_NICKNAME ? static_cast<const PT_CHANNEL_NICKNAME *>(Packet()) : nullptr;
}
const PT_CHANNEL_NICKNAME_SUCC_U *Packet_as_PT_CHANNEL_NICKNAME_SUCC_U() const {
return Packet_type() == MESSAGE_ID_PT_CHANNEL_NICKNAME_SUCC_U ? static_cast<const PT_CHANNEL_NICKNAME_SUCC_U *>(Packet()) : nullptr;
}
const PT_CHANNEL_NICKNAME_FAIL_U *Packet_as_PT_CHANNEL_NICKNAME_FAIL_U() const {
return Packet_type() == MESSAGE_ID_PT_CHANNEL_NICKNAME_FAIL_U ? static_cast<const PT_CHANNEL_NICKNAME_FAIL_U *>(Packet()) : nullptr;
}
bool Verify(flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<uint8_t>(verifier, VT_PACKET_TYPE) &&
VerifyOffset(verifier, VT_PACKET) &&
VerifyMESSAGE_ID(verifier, Packet(), Packet_type()) &&
verifier.EndTable();
}
};
template<> inline const PT_CHANNEL_NICKNAME *Message::Packet_as<PT_CHANNEL_NICKNAME>() const {
return Packet_as_PT_CHANNEL_NICKNAME();
}
template<> inline const PT_CHANNEL_NICKNAME_SUCC_U *Message::Packet_as<PT_CHANNEL_NICKNAME_SUCC_U>() const {
return Packet_as_PT_CHANNEL_NICKNAME_SUCC_U();
}
template<> inline const PT_CHANNEL_NICKNAME_FAIL_U *Message::Packet_as<PT_CHANNEL_NICKNAME_FAIL_U>() const {
return Packet_as_PT_CHANNEL_NICKNAME_FAIL_U();
}
struct MessageBuilder {
flatbuffers::FlatBufferBuilder &fbb_;
flatbuffers::uoffset_t start_;
void add_Packet_type(MESSAGE_ID Packet_type) {
fbb_.AddElement<uint8_t>(Message::VT_PACKET_TYPE, static_cast<uint8_t>(Packet_type), 0);
}
void add_Packet(flatbuffers::Offset<void> Packet) {
fbb_.AddOffset(Message::VT_PACKET, Packet);
}
explicit MessageBuilder(flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
MessageBuilder &operator=(const MessageBuilder &);
flatbuffers::Offset<Message> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = flatbuffers::Offset<Message>(end);
return o;
}
};
inline flatbuffers::Offset<Message> CreateMessage(
flatbuffers::FlatBufferBuilder &_fbb,
MESSAGE_ID Packet_type = MESSAGE_ID_NONE,
flatbuffers::Offset<void> Packet = 0) {
MessageBuilder builder_(_fbb);
builder_.add_Packet(Packet);
builder_.add_Packet_type(Packet_type);
return builder_.Finish();
}
struct PT_CHANNEL_NICKNAME FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
enum {
VT_USER_ID = 4,
VT_NICK_NAME = 6
};
const flatbuffers::String *USER_ID() const {
return GetPointer<const flatbuffers::String *>(VT_USER_ID);
}
const flatbuffers::String *NICK_NAME() const {
return GetPointer<const flatbuffers::String *>(VT_NICK_NAME);
}
bool Verify(flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyOffset(verifier, VT_USER_ID) &&
verifier.VerifyString(USER_ID()) &&
VerifyOffset(verifier, VT_NICK_NAME) &&
verifier.VerifyString(NICK_NAME()) &&
verifier.EndTable();
}
};
struct PT_CHANNEL_NICKNAMEBuilder {
flatbuffers::FlatBufferBuilder &fbb_;
flatbuffers::uoffset_t start_;
void add_USER_ID(flatbuffers::Offset<flatbuffers::String> USER_ID) {
fbb_.AddOffset(PT_CHANNEL_NICKNAME::VT_USER_ID, USER_ID);
}
void add_NICK_NAME(flatbuffers::Offset<flatbuffers::String> NICK_NAME) {
fbb_.AddOffset(PT_CHANNEL_NICKNAME::VT_NICK_NAME, NICK_NAME);
}
explicit PT_CHANNEL_NICKNAMEBuilder(flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
PT_CHANNEL_NICKNAMEBuilder &operator=(const PT_CHANNEL_NICKNAMEBuilder &);
flatbuffers::Offset<PT_CHANNEL_NICKNAME> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = flatbuffers::Offset<PT_CHANNEL_NICKNAME>(end);
return o;
}
};
inline flatbuffers::Offset<PT_CHANNEL_NICKNAME> CreatePT_CHANNEL_NICKNAME(
flatbuffers::FlatBufferBuilder &_fbb,
flatbuffers::Offset<flatbuffers::String> USER_ID = 0,
flatbuffers::Offset<flatbuffers::String> NICK_NAME = 0) {
PT_CHANNEL_NICKNAMEBuilder builder_(_fbb);
builder_.add_NICK_NAME(NICK_NAME);
builder_.add_USER_ID(USER_ID);
return builder_.Finish();
}
inline flatbuffers::Offset<PT_CHANNEL_NICKNAME> CreatePT_CHANNEL_NICKNAMEDirect(
flatbuffers::FlatBufferBuilder &_fbb,
const char *USER_ID = nullptr,
const char *NICK_NAME = nullptr) {
return PT_VERSION_1000000::CreatePT_CHANNEL_NICKNAME(
_fbb,
USER_ID ? _fbb.CreateString(USER_ID) : 0,
NICK_NAME ? _fbb.CreateString(NICK_NAME) : 0);
}
struct PT_CHANNEL_NICKNAME_SUCC_U FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
enum {
VT_USER_ID = 4,
VT_NICK_NAME = 6
};
const flatbuffers::String *USER_ID() const {
return GetPointer<const flatbuffers::String *>(VT_USER_ID);
}
const flatbuffers::String *NICK_NAME() const {
return GetPointer<const flatbuffers::String *>(VT_NICK_NAME);
}
bool Verify(flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyOffset(verifier, VT_USER_ID) &&
verifier.VerifyString(USER_ID()) &&
VerifyOffset(verifier, VT_NICK_NAME) &&
verifier.VerifyString(NICK_NAME()) &&
verifier.EndTable();
}
};
struct PT_CHANNEL_NICKNAME_SUCC_UBuilder {
flatbuffers::FlatBufferBuilder &fbb_;
flatbuffers::uoffset_t start_;
void add_USER_ID(flatbuffers::Offset<flatbuffers::String> USER_ID) {
fbb_.AddOffset(PT_CHANNEL_NICKNAME_SUCC_U::VT_USER_ID, USER_ID);
}
void add_NICK_NAME(flatbuffers::Offset<flatbuffers::String> NICK_NAME) {
fbb_.AddOffset(PT_CHANNEL_NICKNAME_SUCC_U::VT_NICK_NAME, NICK_NAME);
}
explicit PT_CHANNEL_NICKNAME_SUCC_UBuilder(flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
PT_CHANNEL_NICKNAME_SUCC_UBuilder &operator=(const PT_CHANNEL_NICKNAME_SUCC_UBuilder &);
flatbuffers::Offset<PT_CHANNEL_NICKNAME_SUCC_U> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = flatbuffers::Offset<PT_CHANNEL_NICKNAME_SUCC_U>(end);
return o;
}
};
inline flatbuffers::Offset<PT_CHANNEL_NICKNAME_SUCC_U> CreatePT_CHANNEL_NICKNAME_SUCC_U(
flatbuffers::FlatBufferBuilder &_fbb,
flatbuffers::Offset<flatbuffers::String> USER_ID = 0,
flatbuffers::Offset<flatbuffers::String> NICK_NAME = 0) {
PT_CHANNEL_NICKNAME_SUCC_UBuilder builder_(_fbb);
builder_.add_NICK_NAME(NICK_NAME);
builder_.add_USER_ID(USER_ID);
return builder_.Finish();
}
inline flatbuffers::Offset<PT_CHANNEL_NICKNAME_SUCC_U> CreatePT_CHANNEL_NICKNAME_SUCC_UDirect(
flatbuffers::FlatBufferBuilder &_fbb,
const char *USER_ID = nullptr,
const char *NICK_NAME = nullptr) {
return PT_VERSION_1000000::CreatePT_CHANNEL_NICKNAME_SUCC_U(
_fbb,
USER_ID ? _fbb.CreateString(USER_ID) : 0,
NICK_NAME ? _fbb.CreateString(NICK_NAME) : 0);
}
struct PT_CHANNEL_NICKNAME_FAIL_U FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
enum {
VT_ERROR_CODE = 4
};
uint32_t ERROR_CODE() const {
return GetField<uint32_t>(VT_ERROR_CODE, 0);
}
bool Verify(flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<uint32_t>(verifier, VT_ERROR_CODE) &&
verifier.EndTable();
}
};
struct PT_CHANNEL_NICKNAME_FAIL_UBuilder {
flatbuffers::FlatBufferBuilder &fbb_;
flatbuffers::uoffset_t start_;
void add_ERROR_CODE(uint32_t ERROR_CODE) {
fbb_.AddElement<uint32_t>(PT_CHANNEL_NICKNAME_FAIL_U::VT_ERROR_CODE, ERROR_CODE, 0);
}
explicit PT_CHANNEL_NICKNAME_FAIL_UBuilder(flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
PT_CHANNEL_NICKNAME_FAIL_UBuilder &operator=(const PT_CHANNEL_NICKNAME_FAIL_UBuilder &);
flatbuffers::Offset<PT_CHANNEL_NICKNAME_FAIL_U> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = flatbuffers::Offset<PT_CHANNEL_NICKNAME_FAIL_U>(end);
return o;
}
};
inline flatbuffers::Offset<PT_CHANNEL_NICKNAME_FAIL_U> CreatePT_CHANNEL_NICKNAME_FAIL_U(
flatbuffers::FlatBufferBuilder &_fbb,
uint32_t ERROR_CODE = 0) {
PT_CHANNEL_NICKNAME_FAIL_UBuilder builder_(_fbb);
builder_.add_ERROR_CODE(ERROR_CODE);
return builder_.Finish();
}
inline bool VerifyMESSAGE_ID(flatbuffers::Verifier &verifier, const void *obj, MESSAGE_ID type) {
switch (type) {
case MESSAGE_ID_NONE: {
return true;
}
case MESSAGE_ID_PT_CHANNEL_NICKNAME: {
auto ptr = reinterpret_cast<const PT_CHANNEL_NICKNAME *>(obj);
return verifier.VerifyTable(ptr);
}
case MESSAGE_ID_PT_CHANNEL_NICKNAME_SUCC_U: {
auto ptr = reinterpret_cast<const PT_CHANNEL_NICKNAME_SUCC_U *>(obj);
return verifier.VerifyTable(ptr);
}
case MESSAGE_ID_PT_CHANNEL_NICKNAME_FAIL_U: {
auto ptr = reinterpret_cast<const PT_CHANNEL_NICKNAME_FAIL_U *>(obj);
return verifier.VerifyTable(ptr);
}
default: return false;
}
}
inline bool VerifyMESSAGE_IDVector(flatbuffers::Verifier &verifier, const flatbuffers::Vector<flatbuffers::Offset<void>> *values, const flatbuffers::Vector<uint8_t> *types) {
if (!values || !types) return !values && !types;
if (values->size() != types->size()) return false;
for (flatbuffers::uoffset_t i = 0; i < values->size(); ++i) {
if (!VerifyMESSAGE_ID(
verifier, values->Get(i), types->GetEnum<MESSAGE_ID>(i))) {
return false;
}
}
return true;
}
inline const PT_VERSION_1000000::Message *GetMessage(const void *buf) {
return flatbuffers::GetRoot<PT_VERSION_1000000::Message>(buf);
}
inline const PT_VERSION_1000000::Message *GetSizePrefixedMessage(const void *buf) {
return flatbuffers::GetSizePrefixedRoot<PT_VERSION_1000000::Message>(buf);
}
inline bool VerifyMessageBuffer(
flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<PT_VERSION_1000000::Message>(nullptr);
}
inline bool VerifySizePrefixedMessageBuffer(
flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<PT_VERSION_1000000::Message>(nullptr);
}
inline void FinishMessageBuffer(
flatbuffers::FlatBufferBuilder &fbb,
flatbuffers::Offset<PT_VERSION_1000000::Message> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedMessageBuffer(
flatbuffers::FlatBufferBuilder &fbb,
flatbuffers::Offset<PT_VERSION_1000000::Message> root) {
fbb.FinishSizePrefixed(root);
}
} // namespace PT_VERSION_1000000
#endif // FLATBUFFERS_GENERATED_FLATBUFSTUDY_PT_VERSION_1000000_H_
접기