ソケット接続を介してシリアル化して送信する必要があるオブジェクトの小さな階層があります。オブジェクトをシリアル化してから、オブジェクトの種類に基づいて逆シリアル化する必要があります。 C++でこれを行う簡単な方法はありますか(Javaにあるように)?
C++シリアル化のオンラインコードサンプルまたはチュートリアルはありますか?
編集:明確にするために、オブジェクトをバイト配列に変換してからオブジェクトに戻す方法を探しています。ソケット伝送を処理できます。
シリアライゼーションについて言えば、 boost serialization API が思い浮かびます。シリアル化されたデータをネット経由で送信するには、Berkeleyソケットまたは asio library を使用します。
編集:
オブジェクトをバイト配列にシリアル化する場合は、次の方法でブーストシリアライザーを使用できます(チュートリアルサイトから入手)。
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};
実際のシリアル化は非常に簡単です。
#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);
// create class instance
const gps_position g(35, 59, 24.567f);
// save data to archive
{
boost::archive::binary_oarchive oa(ofs);
// write class instance to archive
oa << g;
// archive and stream closed when destructors are called
}
逆シリアル化も同様の方法で機能します。
また、ポインターの直列化(房などの複雑なデータ構造は問題ありません)、派生クラスを処理できるメカニズムがあり、バイナリまたはテキストの直列化を選択できます。さらに、すべてのSTLコンテナがデフォルトでサポートされています。
単純な型を扱う場合、次のことができます。
object o;
socket.write(&o, sizeof(o));
概念実証または最初のドラフトとしては問題ないので、チームの他のメンバーは他の部分で作業を続けることができます。
しかし、遅かれ早かれ、通常遅かれ早かれ、これはあなたを傷つけるでしょう!
次の問題が発生します。
(さらに、受信側で開梱する内容を知る必要があります。)
これを改善するには、クラスごとに独自のマーシャリング/アンマーシャリングメソッドを開発します。 (理想的には仮想であるため、サブクラスで拡張できます。)いくつかの単純なマクロを使用すると、さまざまな基本タイプをビッグ/リトルエンディアン中立の順序で非常に迅速に記述できます。
しかし、この種の単調な作業は、 boostのシリアル化ライブラリ を介して処理する方がはるかに優れており、より簡単です。
シリアル化とは、オブジェクトをバイナリデータに変換することです。逆シリアル化とは、データからオブジェクトを再作成することを意味します。
シリアル化するとき、バイトを_uint8_t
_ベクトルにプッシュします。シリアル化を解除すると、_uint8_t
_ベクトルからバイトを読み取ります。
ものをシリアル化するときに使用できるパターンは確かにあります。
各シリアライズ可能クラスには、提供されたベクターにバイナリ表現を書き込むserialize(std::vector<uint8_t> &binaryData)
または同様の署名付き関数が必要です。次に、この関数は、このベクトルをそのメンバーのシリアル化関数に渡し、彼らも自分のものを書き込むことができます。
データ表現はアーキテクチャによって異なる場合があるためです。データの表現方法を見つける必要があります。
基本から始めましょう:
バイトをリトルエンディアン順に書き込むだけです。または、サイズが重要な場合は、varint表現を使用します。
リトルエンディアン順のシリアル化:
_data.Push_back(integer32 & 0xFF);
data.Push_back((integer32 >> 8) & 0xFF);
data.Push_back((integer32 >> 16) & 0xFF);
data.Push_back((integer32 >> 24) & 0xFF);
_
リトルエンディアン順からの逆シリアル化:
_integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
_
私の知る限り、IEEE 754はここで独占しています。私は、フロートに別のものを使用する主流のアーキテクチャを知りません。異なる可能性がある唯一のものは、バイト順です。リトルエンディアンを使用するアーキテクチャもあれば、ビッグエンディアンのバイト順を使用するアーキテクチャもあります。これは、受信側のバイトを大きくする順序に注意する必要があることを意味します。別の違いは、非正規値、無限値、NAN値の処理です。ただし、これらの値を回避する限り、問題ありません。
シリアル化:
_uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.Push_back(mem[0]);
data.Push_back(mem[1]);
...
_
逆シリアル化は逆方向に実行します。アーキテクチャのバイト順序に注意してください!
まず、エンコーディングに同意する必要があります。 UTF-8は一般的です。次に、長さを接頭辞として保存します。まず、前述の方法を使用して文字列の長さを保存し、次に文字列をバイト単位で書き込みます。
それらは文字列と同じです。最初に配列のサイズを表す整数をシリアル化し、次に配列内の各オブジェクトをシリアル化します。
前に言ったように、ベクターにコンテンツを追加するserialize
メソッドが必要です。オブジェクトのシリアル化を解除するには、バイトストリームを取得するコンストラクターが必要です。 istream
にすることもできますが、最も単純な場合は、単に参照_uint8_t
_ポインターにすることができます。コンストラクターは、ストリームから必要なバイトを読み取り、オブジェクトのフィールドを設定します。システムが適切に設計され、オブジェクトフィールド順でフィールドをシリアル化する場合、ストリームを初期化リストのフィールドのコンストラクターに渡し、正しい順序で逆シリアル化することができます。
まず、これらのオブジェクトが本当にシリアライズしたいものかどうかを確認する必要があります。これらのオブジェクトのインスタンスが宛先に存在する場合、それらをシリアル化する必要はありません。
これで、ポインターが指すオブジェクトをシリアル化する必要があることがわかりました。それらが使用するプログラムでのみ有効なポインターの問題。ポインターをシリアル化することはできません。オブジェクトでの使用を停止する必要があります。代わりに、オブジェクトプールを作成します。このオブジェクトプールは、基本的に「ボックス」を含む動的配列です。これらのボックスには参照カウントがあります。ゼロ以外の参照カウントはライブオブジェクトを示し、ゼロは空のスロットを示します。次に、オブジェクトへのポインターではなく、配列内のインデックスを格納するshared_ptrと同様のスマートポインターを作成します。また、nullポインターを示すインデックスについても同意する必要があります。 -1。
基本的にここで行ったことは、ポインタを配列インデックスに置き換えたものです。これで、シリアル化するときに、この配列インデックスを通常どおりシリアル化できます。オブジェクトが宛先システムのメモリ内のどこにあるかを心配する必要はありません。それらが同じオブジェクトプールを持っていることを確認してください。
そのため、オブジェクトプールをシリアル化する必要があります。しかし、どれですか?オブジェクトグラフをシリアル化すると、オブジェクトだけをシリアル化するのではなく、システム全体をシリアル化することになります。これは、システムのシリアル化がシステムの一部から始まってはならないことを意味します。これらのオブジェクトは、システムの残りの部分を心配する必要はありません。配列インデックスをシリアル化するだけで十分です。システムのシリアル化を調整し、関連するオブジェクトプールを調べてすべてをシリアル化するシステムシリアライザールーチンが必要です。
受信側では、すべての配列とその中のオブジェクトが逆シリアル化され、目的のオブジェクトグラフが再作成されます。
オブジェクトにポインターを格納しないでください。これらの関数へのポインターを含む静的配列を持ち、オブジェクトにインデックスを保存します。
両方のプログラムはこのテーブルをそれらのシェルフにコンパイルしているので、インデックスだけを使用しても機能します。
シリアル化可能な型ではポインターを避け、代わりに配列インデックスを使用する必要があると述べたため、ポリモーフィズムはポインターを必要とするため機能しません。
型タグと共用体でこれを回避する必要があります。
上記のすべての上に。ソフトウェアの異なるバージョンを相互運用することができます。
この場合、各オブジェクトは、バージョンを示すために、シリアル化の最初にバージョン番号を書き込む必要があります。
反対側のオブジェクトをロードするとき、新しいオブジェクトは古い表現を処理できるかもしれませんが、古いものは新しいものを処理できないため、これに関する例外をスローする必要があります。
何かが変わるたびに、バージョン番号を上げる必要があります。
したがって、これをまとめるために、シリアル化は複雑になる可能性があります。しかし幸いなことに、プログラムのすべてをシリアル化する必要はありません。ほとんどの場合、プロトコルメッセージのみがシリアル化されます。これは単純な古い構造体です。そのため、上記で説明した複雑なトリックはあまり必要ありません。