protobuf javaアプリケーションで反射により動的にオブジェクトを作成する
6760 ワード
-コンテンツの復元開始-
最近、protobufのデータフォーマットを用いてフロントバックグラウンド伝送を行うゲームが作成され、protobufがクライアントのデータを受信する際にxxxのようなデータ型が必要であることに苦しんでいる.parseForm(...)は、クライアント要求を受信する際に、クライアントが伝達するデータ型をサーバに知らせる必要がある.クライアントのリクエストデータは多種多様であるため、サーバ側はクライアントのリクエストがどのタイプなのか分からないため、サーバ側のプログラミングに多くの面倒をもたらし、一歩も歩けない.解決策がないのか、答えはもちろんある.次によく使われる方法についてお話しします.(本稿を読む前にprotobufの基本的な文法と基本的な使い方を理解することをお勧めします)
1.第1の方法も最も簡単な方法で、アプリケーション全体でprotoファイルを1つだけ定義すると、すべてのリクエストが1つのタイプになり、サーバ側はどのリクエストデータも同じオブジェクトで解析するので、リクエストデータをどのように解析するか悩む必要はありません.次の列のようにします.
まずPBMessageを貼ります.protoファイル
//クライアント要求およびサービス側応答データプロトコルoption java_outer_classname = “PBMessageProto”;
package com.ppsea.message; import “main/resources/message/DataMsg.proto”;
Message PBMessage{optional int 32 playerId=1;//プレイヤーid required int 32 actionCode=2;//オペレーティングコードid optional bytes data=5;//送信または応答のデータoptional DataMsg dataMsg=6;//サーバ側プッシュデータoptional string sessionKey=7;//要求の検証コードoptional int 32 sessionId=8;//現在の要求の表示}上記コードのように、アプリケーション全体がPBMessageに基づいている.proto転送、protobuf構文のoptional修飾子に気づいた.彼はこのフィールドが必須ではないことを示している.つまり、クライアントの異なる要求に対して、その要求時に使うフィールドの値を記入するだけでいい.他のフィールドの値は気にしないでください.そうすれば、いろいろな要求をシミュレートすることができます.では、次はPBMessageを使います.parseForm(byte_PBMesage)//byte_PBMessageはクライアント要求データを表し,要求の解析が完了する.同時に、(required int 32 actionCode=2;//操作コードid)、requiredはこのフィールドが必要であることを示し、前の要求はすでに解析されており、ここでactionCodeを手に入れると、どのActionイベントでこの要求を処理すべきかを知ることができる(前の提案はactionCodeからActionへのマッピング関係テーブル:Mapを維持しなければならない).これでリクエスト全体の解析と処理が完了しました.
次に、アプリケーション全体のメッセージフォーマットが一致して統一され、操作が簡単であるという利点と欠点について説明します.欠点:あまり統一しすぎると、柔軟ではありません.要求が少なく、メッセージフォーマットの少ない小型アプリケーションは無理に使えます.メッセージフォーマットが多ければ、このような方法で使うと肥大化し、管理が不便になり、プログラム設計の意義を失います.
2.2つ目の方法は、私が今回使った方法でもあります.提案中の方式の限界に苦しみ、本人はネット上で資料を収集し、protobuf java版のソースコードを見ることで、折衷の方式を発見した.まずprotobufソースコードで提供されている、DynamicMessageクラス(名前の通り動的メッセージクラス、目の前に木がある)を見てみましょう.AbstractMessageクラスを継承しています.最初に作成したPBMessageクラスとの違いを比較すると、PBMessageクラスはGeneratedMessageクラスを継承し、GeneratedMessageクラスはAbstractMessageクラスを継承していることがわかります.これで共通クラスAbstractMessageを発見し、DynamicMessageの機能を再確認するとともに、AbstractParserクラス(PBMessageクラスにAbstractParserクラスのオブジェクトを保持し、要求データを解析するために使用される)を参照します.これはParserインタフェースを継承し、その部分的方法を見てみましょう.
public abstract MessageType parseFrom(byte[] paramArrayOfByte) throws InvalidProtocolBufferException;
public abstract MessageType parseFrom(InputStream paramInputStream) throws InvalidProtocolBufferException;
public abstract MessageType parseFrom(InputStream paramInputStream,ExtensionRegistryLite paramExtensionRegistryLite) throws InvalidProtocolBufferException; ,DynamicMessageで提供されている方法を見てみましょう.
public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data) throws InvalidProtocolBufferException {
}
public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data, ExtensionRegistry extensionRegistry)throws InvalidProtocolBufferException {
}
public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input) throws IOException {
}
public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input, ExtensionRegistry extensionRegistry)throws IOException { return ((Builder) newBuilder(type).mergeFrom(input, extensionRegistry)).buildParsed(); }
彼らの方法の類似点が発見され,ここでは,DynamicMessage=AbstractMessage+AbstractParser=PBMessage,すなわちDynamicMessageがAbstractMessage(リクエストメッセージオブジェクト)を継承するとともにAbstractParser(リクエストメッセージデータ解析)のデータ解析機能を間接的に実現する等量関係を用いて比喩することができる.今私たちが唯一欠けているのはDescriptorsです.Descriptor(メッセージの記述)オブジェクト、このオブジェクトはどのように手に入れますか?ここで肯定的に言えば、違います.protoはここのDescriptorsを要求した.Descriptorは違います.ここではPBMessageオブジェクトに戻り、次のような方法を発見しました.
public final class PBMessageProto {
………..//ここではいくつかの行public static final comを省略する.google.protobuf.Descriptors.Descriptor getDescriptor() {
これが私たちが探しているものではないでしょうか.この方法でDescriptorを手に入れることができるのではないでしょうか.ここで重要なのは、まずPBMessageオブジェクト(ここではそれを代表として、他の.protoオブジェクトを使用することができる)があればDescriptorsを得ることができることを理解することです.Descriptorオブジェクト、Descriptor sがあります.DescriptorオブジェクトはDynamicMessageオブジェクトを作成し、DynamicMessageがあれば対応要求を解析できます.コードを次に示します.
//メッセージ操作コードとメッセージオブジェクトを格納するMapdescriptorMap=new Map;//メッセージ記述オブジェクトをdescriptorMapに追加します.add(100,PBMessage.getDescriptor()); descriptorMap.add(xxx,xxx); このようにDescriptorができて、実はもっとよくすることができて、反射のメカニズムを通じて、Mapの中で操作コードと対応するprotoのオブジェクトのクラス名だけを保存して、更に反射の方式を通じてprotoのオブジェクトを作成してそのgetDescriptor()の方法を獲得します.これにより、コンフィギュレーションファイルでオペレーティングコードとprotoオブジェクトの関係を構成できます.
次にクライアントのリクエストを受け入れてみましょう
//クライアント疑似コードclient.send(byte_PBMessage); サーバはリクエストを受け入れて解析し、
//サーバダミーコード
byte[] date=server.accept();
//クライアント操作コードint actionCode;
Descriptor descriptor=descriptorMap.get(actionCode);
//解析要求DynamicMessage req=DynamicMessage.parseFrom(descriptor, date); ここでは、actionCodeはまだ分からないようですが、どうすればいいのでしょうか.クライアントが送信したリクエストメッセージのヘッダにactionCodeを追加します.つまり、オペレーティングコードとprotoメッセージを新しいリクエストに統合してクライアントに送信します.2桁をオペレーティングコードにしてください.では、クライアントはこのようにメッセージを送信するはずです.
//クライアント疑似コードshort actionCode=100;
//actionCode byte[]actionCodeByte=new byte[2];
//バイトストリームに変換するactionCodeByte.set(actionCode.toByteArray());//偽コード、本気にしないでください
//要求ヘッダ付きメッセージの合計長int length=actionCodeByte.length+byte_PBMEssage.length;
byte [] messageByte=new byte[length];
//オペレーティングコードとprotoメッセージをmessageByte=actionCodeByte+byte_PBMEssage;
client.send(messageByte); 次はサーバです.
//サーバ疑似コードbyte[]data=server.accept();//上位2位を取り出すbyte[]actionCodeByte=data.read(0,2);
//actionCodeはint actionCode=actionCodeByte.readShort();
//protoメッセージを取り出すbyte[]byte_PBMessage=data.read(2,data.length); …..次は前のサーバーの疑似コードと同じですDynamicMessage req=DynamicMessage.parseFrom(descriptorMap.get(actionCode, byte_PBMessage); …. これでダイナミックオブジェクトの作成が完了し、次に、第1の方法でメンテナンスされたActionMapがactionCodeによってactionにフェッチされ、DynamicMessage解析の要求を処理します.
あ、もちろんactionCodeもactionNameに変えることができますが、似ています.ここまで悪くないようですが、当時はずっと完璧ではありませんでした.私たちはまだDynamicMessageをPBMessageオブジェクトに変換していないので、後続のactionでDynamicMessageを処理するのはいつも気分が悪く、解決策はDynamicMessageオブジェクトを通じてDescriptorオブジェクトを獲得し、そのすべてのフィールド名と値を獲得することです.次に、このアドレスのこの記事を見てみましょう(フィールドでオブジェクト部分を反射します):http://liufei-fir.iteye.com/blog/1160700,反射によりPBMessageを復元したが,以上は実験に成功し,時間的な理由でソースコードを貼り付けなかった.何か問題があったらみんなに指摘してほしい.
最近、protobufのデータフォーマットを用いてフロントバックグラウンド伝送を行うゲームが作成され、protobufがクライアントのデータを受信する際にxxxのようなデータ型が必要であることに苦しんでいる.parseForm(...)は、クライアント要求を受信する際に、クライアントが伝達するデータ型をサーバに知らせる必要がある.クライアントのリクエストデータは多種多様であるため、サーバ側はクライアントのリクエストがどのタイプなのか分からないため、サーバ側のプログラミングに多くの面倒をもたらし、一歩も歩けない.解決策がないのか、答えはもちろんある.次によく使われる方法についてお話しします.(本稿を読む前にprotobufの基本的な文法と基本的な使い方を理解することをお勧めします)
1.第1の方法も最も簡単な方法で、アプリケーション全体でprotoファイルを1つだけ定義すると、すべてのリクエストが1つのタイプになり、サーバ側はどのリクエストデータも同じオブジェクトで解析するので、リクエストデータをどのように解析するか悩む必要はありません.次の列のようにします.
まずPBMessageを貼ります.protoファイル
//クライアント要求およびサービス側応答データプロトコルoption java_outer_classname = “PBMessageProto”;
package com.ppsea.message; import “main/resources/message/DataMsg.proto”;
Message PBMessage{optional int 32 playerId=1;//プレイヤーid required int 32 actionCode=2;//オペレーティングコードid optional bytes data=5;//送信または応答のデータoptional DataMsg dataMsg=6;//サーバ側プッシュデータoptional string sessionKey=7;//要求の検証コードoptional int 32 sessionId=8;//現在の要求の表示}上記コードのように、アプリケーション全体がPBMessageに基づいている.proto転送、protobuf構文のoptional修飾子に気づいた.彼はこのフィールドが必須ではないことを示している.つまり、クライアントの異なる要求に対して、その要求時に使うフィールドの値を記入するだけでいい.他のフィールドの値は気にしないでください.そうすれば、いろいろな要求をシミュレートすることができます.では、次はPBMessageを使います.parseForm(byte_PBMesage)//byte_PBMessageはクライアント要求データを表し,要求の解析が完了する.同時に、(required int 32 actionCode=2;//操作コードid)、requiredはこのフィールドが必要であることを示し、前の要求はすでに解析されており、ここでactionCodeを手に入れると、どのActionイベントでこの要求を処理すべきかを知ることができる(前の提案はactionCodeからActionへのマッピング関係テーブル:Mapを維持しなければならない).これでリクエスト全体の解析と処理が完了しました.
次に、アプリケーション全体のメッセージフォーマットが一致して統一され、操作が簡単であるという利点と欠点について説明します.欠点:あまり統一しすぎると、柔軟ではありません.要求が少なく、メッセージフォーマットの少ない小型アプリケーションは無理に使えます.メッセージフォーマットが多ければ、このような方法で使うと肥大化し、管理が不便になり、プログラム設計の意義を失います.
2.2つ目の方法は、私が今回使った方法でもあります.提案中の方式の限界に苦しみ、本人はネット上で資料を収集し、protobuf java版のソースコードを見ることで、折衷の方式を発見した.まずprotobufソースコードで提供されている、DynamicMessageクラス(名前の通り動的メッセージクラス、目の前に木がある)を見てみましょう.AbstractMessageクラスを継承しています.最初に作成したPBMessageクラスとの違いを比較すると、PBMessageクラスはGeneratedMessageクラスを継承し、GeneratedMessageクラスはAbstractMessageクラスを継承していることがわかります.これで共通クラスAbstractMessageを発見し、DynamicMessageの機能を再確認するとともに、AbstractParserクラス(PBMessageクラスにAbstractParserクラスのオブジェクトを保持し、要求データを解析するために使用される)を参照します.これはParserインタフェースを継承し、その部分的方法を見てみましょう.
public abstract MessageType parseFrom(byte[] paramArrayOfByte) throws InvalidProtocolBufferException;
public abstract MessageType parseFrom(InputStream paramInputStream) throws InvalidProtocolBufferException;
public abstract MessageType parseFrom(InputStream paramInputStream,ExtensionRegistryLite paramExtensionRegistryLite) throws InvalidProtocolBufferException; ,DynamicMessageで提供されている方法を見てみましょう.
public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data) throws InvalidProtocolBufferException {
return ((Builder) newBuilder(type).mergeFrom(data)).buildParsed();
}
public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data, ExtensionRegistry extensionRegistry)throws InvalidProtocolBufferException {
return ((Builder) newBuilder(type).mergeFrom(data, extensionRegistry)).buildParsed();
}
public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input) throws IOException {
return ((Builder) newBuilder(type).mergeFrom(input)).buildParsed();
}
public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input, ExtensionRegistry extensionRegistry)throws IOException { return ((Builder) newBuilder(type).mergeFrom(input, extensionRegistry)).buildParsed(); }
彼らの方法の類似点が発見され,ここでは,DynamicMessage=AbstractMessage+AbstractParser=PBMessage,すなわちDynamicMessageがAbstractMessage(リクエストメッセージオブジェクト)を継承するとともにAbstractParser(リクエストメッセージデータ解析)のデータ解析機能を間接的に実現する等量関係を用いて比喩することができる.今私たちが唯一欠けているのはDescriptorsです.Descriptor(メッセージの記述)オブジェクト、このオブジェクトはどのように手に入れますか?ここで肯定的に言えば、違います.protoはここのDescriptorsを要求した.Descriptorは違います.ここではPBMessageオブジェクトに戻り、次のような方法を発見しました.
public final class PBMessageProto {
..................//
public static final class PBMessage extendscom.google.protobuf.GeneratedMessage implements PBMessageOrBuilder {
………..//ここではいくつかの行public static final comを省略する.google.protobuf.Descriptors.Descriptor getDescriptor() {
return com.ppsea.message.PBMessageProto.internal_static_com_ppsea_message_PBMessage_descriptor;
}
これが私たちが探しているものではないでしょうか.この方法でDescriptorを手に入れることができるのではないでしょうか.ここで重要なのは、まずPBMessageオブジェクト(ここではそれを代表として、他の.protoオブジェクトを使用することができる)があればDescriptorsを得ることができることを理解することです.Descriptorオブジェクト、Descriptor sがあります.DescriptorオブジェクトはDynamicMessageオブジェクトを作成し、DynamicMessageがあれば対応要求を解析できます.コードを次に示します.
//メッセージ操作コードとメッセージオブジェクトを格納するMapdescriptorMap=new Map;//メッセージ記述オブジェクトをdescriptorMapに追加します.add(100,PBMessage.getDescriptor()); descriptorMap.add(xxx,xxx); このようにDescriptorができて、実はもっとよくすることができて、反射のメカニズムを通じて、Mapの中で操作コードと対応するprotoのオブジェクトのクラス名だけを保存して、更に反射の方式を通じてprotoのオブジェクトを作成してそのgetDescriptor()の方法を獲得します.これにより、コンフィギュレーションファイルでオペレーティングコードとprotoオブジェクトの関係を構成できます.
次にクライアントのリクエストを受け入れてみましょう
//クライアント疑似コードclient.send(byte_PBMessage); サーバはリクエストを受け入れて解析し、
//サーバダミーコード
byte[] date=server.accept();
//クライアント操作コードint actionCode;
Descriptor descriptor=descriptorMap.get(actionCode);
//解析要求DynamicMessage req=DynamicMessage.parseFrom(descriptor, date); ここでは、actionCodeはまだ分からないようですが、どうすればいいのでしょうか.クライアントが送信したリクエストメッセージのヘッダにactionCodeを追加します.つまり、オペレーティングコードとprotoメッセージを新しいリクエストに統合してクライアントに送信します.2桁をオペレーティングコードにしてください.では、クライアントはこのようにメッセージを送信するはずです.
//クライアント疑似コードshort actionCode=100;
//actionCode byte[]actionCodeByte=new byte[2];
//バイトストリームに変換するactionCodeByte.set(actionCode.toByteArray());//偽コード、本気にしないでください
//要求ヘッダ付きメッセージの合計長int length=actionCodeByte.length+byte_PBMEssage.length;
byte [] messageByte=new byte[length];
//オペレーティングコードとprotoメッセージをmessageByte=actionCodeByte+byte_PBMEssage;
client.send(messageByte); 次はサーバです.
//サーバ疑似コードbyte[]data=server.accept();//上位2位を取り出すbyte[]actionCodeByte=data.read(0,2);
//actionCodeはint actionCode=actionCodeByte.readShort();
//protoメッセージを取り出すbyte[]byte_PBMessage=data.read(2,data.length); …..次は前のサーバーの疑似コードと同じですDynamicMessage req=DynamicMessage.parseFrom(descriptorMap.get(actionCode, byte_PBMessage); …. これでダイナミックオブジェクトの作成が完了し、次に、第1の方法でメンテナンスされたActionMapがactionCodeによってactionにフェッチされ、DynamicMessage解析の要求を処理します.
あ、もちろんactionCodeもactionNameに変えることができますが、似ています.ここまで悪くないようですが、当時はずっと完璧ではありませんでした.私たちはまだDynamicMessageをPBMessageオブジェクトに変換していないので、後続のactionでDynamicMessageを処理するのはいつも気分が悪く、解決策はDynamicMessageオブジェクトを通じてDescriptorオブジェクトを獲得し、そのすべてのフィールド名と値を獲得することです.次に、このアドレスのこの記事を見てみましょう(フィールドでオブジェクト部分を反射します):http://liufei-fir.iteye.com/blog/1160700,反射によりPBMessageを復元したが,以上は実験に成功し,時間的な理由でソースコードを貼り付けなかった.何か問題があったらみんなに指摘してほしい.