Googleプロトコルバッファランダムメッセージマネージャー

自由な日が現れたので、google :: protobufライブラリーをいじることに決めました。 このライブラリは、構造化データをエンコードおよびデコードする機能を提供します。 このライブラリに基づいて、任意のメッセージを処理できる単純なディスパッチャを作成します。 このディスパッチャの特徴は、送信されたメッセージのタイプを認識せず、登録されたハンドラーを使用してメッセージを処理するだけであることです。



protobufライブラリの簡単な説明



そのため、まず、google :: protobufライブラリを簡単に検討します。これには2つのコンポーネントがあります。

実際にはライブラリ自体+ヘッダーファイル

* .protoファイルコンパイラ-メッセージ記述からC ++クラスを生成します(他のプログラミング言語(Java、Pythonなど)用に生成することもできます)

メッセージの説明は、クラスが生成される別のファイルに作成されます。構文は非常に簡単です。

package sample.proto; message ServerStatusAnswer { optional int32 threadCount = 1; repeated string listeners = 2; }
      
      



ここでは、2つのオプションフィールドを持つServerStatusAnswerメッセージについて説明します。

この説明は、たとえば次のメッセージによって満たされます。

 ServerStatusAnswer { threadCount = 3 listeners = { "one", "two" } }
      
      



実際、protobuf形式はバイナリです。ここでは、わかりやすくするために、読み取り可能な形式でメッセージを提供しました。



コンパイラーは、C ++コードを自動的に生成して、そのようなメッセージをシリアライズおよびデシリアライズします。 protobufライブラリは、ファイル、ストリーム、バッファへのシリアル化という追加機能も提供します。



私はビルドシステムとしてCMakeを使用しており、既にprotobufをサポートしています。

 cmake_minimum_required(VERSION 2.8) project(ProtobufTests) find_package(Protobuf REQUIRED) include_directories(${PROTOBUF_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) #... set (ProtobufTestsProtoSources Message.proto ServerStatus.proto Echo.proto ) #... PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${ProtobufTestsProtoSources}) add_executable(ProtobufTests ${ProtobufTestsSources} ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(ProtobufTests #... ${PROTOBUF_LIBRARY} )
      
      



PROTOBUF_GENERATE_CPP-このマクロは、* .protoファイルごとにprotocコンパイラーを呼び出し、対応するcppおよびhファイルを生成してアセンブリに追加します。

すべてが自動的に行われ、スクワットを追加する必要はありません(* nixでは、追加のThreadsパッケージとリンカーの対応するフラグが必要になる場合があります)。



マネージャーの説明



ある種のメッセージを受け入れ、適切なハンドラーを呼び出し、受信したメッセージに応答を送信するメッセージマネージャーを作成することにしました。 この場合、ディスパッチャは自分に送信されたメッセージのタイプを知らないはずです。 これは、操作中にディスパッチャが適切なハンドラーを追加または削除する場合に必要になることがあります(たとえば、適切な拡張モデル* .dll、* .soをロードすることにより)。



任意のメッセージを処理するには、抽象メッセージを処理するクラスが必要です。 明らかに、* .protoファイルにメッセージの説明がある場合、コンパイラーは対応するクラスを生成しますが、残念ながらそれらはすべてgoogle :: protobuf :: Messageから継承します。 このクラスでは、メッセージからすべてのデータを抽出することは問題です(これは原則として行うことができますが、その後、多くの不必要な作業を行うことになります)。

声明は救助に来ます:「抽象化のレベルが多すぎる問題を除いて、追加の抽象化レベルを導入することで、どんな問題も解決できます。」

メッセージのタイプの定義をメッセージ自体から分離する必要があります。これは次の方法で行うことができます。

 package sample.proto; message Message { required string id = 1; optional bytes data = 2; }
      
      



メッセージを別のメッセージ内にパックします。 したがって、ディスパッチャはidフィールドで対応するメッセージハンドラを検索します。

 #ifndef MESSAGEDISPATCHER_H #define MESSAGEDISPATCHER_H #include <map> #include <stdexcept> #include <boost/noncopyable.hpp> #include <boost/smart_ptr/shared_ptr.hpp> #include "message.pb.h" class MessageProcessingError: public std::runtime_error { public: MessageProcessingError(const std::string & e): std::runtime_error(e) { } }; class MessageProcessorBase: private boost::noncopyable { public: virtual ~MessageProcessorBase() { } virtual std::string id() const = 0; virtual sample::proto::Message process(const sample::proto::Message & query) = 0; }; typedef boost::shared_ptr<MessageProcessorBase> MessageProcessorBasePtr; class MessageDispatcher { public: MessageDispatcher(); void addProcessor(MessageProcessorBasePtr processor); sample::proto::Message dispatch(const sample::proto::Message & query); typedef std::map<std::string, MessageProcessorBasePtr> DispatcherImplType; const DispatcherImplType & impl() const; private: DispatcherImplType mImpl; }; #endif // MESSAGEDISPATCHER_H
      
      



しかし今では、各ハンドラーがサンプル:: proto :: Messageメッセージを独自のメッセージにアンパックする必要があることがわかります。 そして、このようなハンドラーごとにこのプロセスが複製されます。 コードの重複を避けたいので、 Type Erasureパターンを見てみましょう。 このパターンにより、処理されているエンティティのタイプを共通のインターフェースの背後に隠すことができますが、各ハンドラーは、それだけが知っている特定のタイプで動作します。



そのため、実装は非常に簡単です。

 template <typename ProtoQueryT, typename ProtoAnswerT> class ProtoMessageProcessor: public MessageProcessorBase { public: virtual sample::proto::Message process(const sample::proto::Message & query) { ProtoQueryT underlyingQuery; if (!underlyingQuery.ParseFromString(query.data())) { throw MessageProcessingError("Failed to parse query: " + query.ShortDebugString()); } ProtoAnswerT underlyingAnswer = doProcessing(underlyingQuery); sample::proto::Message a; a.set_id(query.id()); if (!underlyingAnswer.SerializeToString(a.mutable_data())) { throw MessageProcessingError("Failed to prepare answer: " + underlyingAnswer.ShortDebugString()); } return a; } private: virtual ProtoAnswerT doProcessing(const ProtoQueryT & query) = 0; };
      
      



仮想関数プロセスを定義しますが、 特定のメッセージで既に機能する仮想関数doProcessも追加します ! この手法は、テンプレートのインスタンス化のメカニズムに基づいています。型は、宣言時ではなく、テンプレートの実際の使用時に置換されます。 また、このクラスはMessageProcessorBaseから継承されるため、このクラスの子孫をディスパッチャーに安全に渡すことができます。 また、このクラスは特定のメッセージをシリアライズおよびデシリアライズし、エラーの場合に例外をスローすることにも注意してください。



最後に、このディスパッチャの使用例を示します。たとえば、2種類のメッセージがあります。

 package sample.proto; message ServerStatusQuery { } message ServerStatusAnswer { optional int32 threadCount = 1; repeated string listeners = 2; }
      
      





 package sample.proto; message EchoQuery { required string msg = 1; } message EchoAnswer { required string echo = 1; }
      
      



説明からわかるように、これらのメッセージはサーバーの内部状態(ServerStatus)を要求し、単に受信した要求(エコー)を返します。 ハンドラー自体の実装は簡単です。ServerStatusのみの実装を示します。

 #ifndef SERVERSTATUSMESSAGEPROCESSOR_H #define SERVERSTATUSMESSAGEPROCESSOR_H #include "MessageDispatcher.h" #include "ServerStatus.pb.h" class ServerStatusMessageProcessor: public ProtoMessageProcessor<sample::proto::ServerStatusQuery, sample::proto::ServerStatusAnswer> { public: typedef sample::proto::ServerStatusQuery query_type; typedef sample::proto::ServerStatusAnswer answer_type; ServerStatusMessageProcessor(MessageDispatcher * dispatcher); virtual std::string id() const; private: MessageDispatcher * mDispatcher; virtual answer_type doProcessing(const query_type & query); }; #endif // SERVERSTATUSMESSAGEPROCESSOR_H
      
      



実装自体:

 #include "ServerStatusMessageProcessor.h" using namespace sample::proto; ServerStatusMessageProcessor::ServerStatusMessageProcessor(MessageDispatcher * dispatcher) : mDispatcher(dispatcher) { } std::string ServerStatusMessageProcessor::id() const { return "ServerStatus"; } ServerStatusAnswer ServerStatusMessageProcessor::doProcessing(const ServerStatusQuery & query) { ServerStatusAnswer s; s.set_threadcount(10); typedef MessageDispatcher::DispatcherImplType::const_iterator md_iterator; const MessageDispatcher::DispatcherImplType & mdImpl = mDispatcher->impl(); for (md_iterator it = mdImpl.begin(); it != mdImpl.end(); ++it) { s.add_listeners(it->first); } return s; }
      
      



仕組みは次のとおりです。

 #include "MessageDispatcher.h" #include "ServerStatusMessageProcessor.h" #include "EchoMessageProcessor.h" #include <iostream> #include <boost/smart_ptr/make_shared.hpp> using namespace sample::proto; int main() { try { MessageDispatcher md; md.addProcessor(boost::make_shared<ServerStatusMessageProcessor>(&md)); md.addProcessor(boost::make_shared<EchoMessageProcessor>()); Message q; q.set_id("ServerStatus"); Message ans = md.dispatch(q); std::cout << "query: " << q.DebugString() << std::endl; std::cout << "answer: " << ans.DebugString() << std::endl; } catch (const std::exception & e) { std::cerr << e.what() << std::endl; } return 0; }
      
      





PSこの記事を書くために使用されました:



githubに投稿された例



All Articles