これを行う最も簡単な方法は、すべてを保持するためにメモリのチャンクを割り当てることです。たとえば、構造体を次のように考えます。
typedef struct A {
int v;
char* str;
} our_struct_t;
これを行う最も簡単な方法は、定義済みのフォーマットを作成し、それをバイト配列にパックすることです。例を示します。
int sLen = 0;
int tLen = 0;
char* serialized = 0;
char* metadata = 0;
char* xval = 0;
char* xstr = 0;
our_struct_t x;
x.v = 10;
x.str = "Our String";
sLen = strlen(x.str); // Assuming null-terminated (which ours is)
tLen = sizeof(int) + sLen; // Our struct has an int and a string - we want the whole string not a mem addr
serialized = malloc(sizeof(char) * (tLen + sizeof(int)); // We have an additional sizeof(int) for metadata - this will hold our string length
metadata = serialized;
xval = serialized + sizeof(int);
xstr = xval + sizeof(int);
*((int*)metadata) = sLen; // Pack our metadata
*((int*)xval) = x.v; // Our "v" value (1 int)
strncpy(xstr, x.str, sLen); // A full copy of our string
したがって、この例では、データをサイズの配列にコピーします2 * sizeof(int) + sLen
。これにより、単一の整数のメタデータ (つまり、文字列の長さ) と構造体から抽出された値が可能になります。逆シリアル化するには、次のように想像できます。
char* serialized = // Assume we have this
char* metadata = serialized;
char* yval = metadata + sizeof(int);
char* ystr = yval + sizeof(int);
our_struct_t y;
int sLen = *((int*)metadata);
y.v = *((int*)yval);
y.str = malloc((sLen + 1) * sizeof(char)); // +1 to null-terminate
strncpy(y.str, ystr, sLen);
y.str[sLen] = '\0';
ご覧のとおり、バイト配列は明確に定義されています。以下に、構造の詳細を示します。
- バイト 0-3 : メタデータ (文字列の長さ)
- バイト 4 ~ 7 : Xv (値)
- バイト 8 - sLen : X.str (値)
この種の明確に定義された構造により、定義された規則に従えば、任意の環境で構造体を再作成できます。この構造体をソケット経由で送信するかどうかは、プロトコルの開発方法によって異なります。最初に、作成したばかりのパケットの全長を含む整数パケットを送信するか、メタデータが最初に/個別に送信されることを期待できます (論理的には個別に、これは技術的にはすべて同時に送信できます)。クライアント側で受信するデータの量を知っています。たとえば、メタデータ値を受け取った場合、構造体を完了するためにバイトが続く10
ことが期待できます。sizeof(int) + 10
一般に、これはおそらく14
バイトです。
編集
コメントで要求されたいくつかの説明をリストします。
(論理的に) 連続したメモリにあるように、文字列の完全なコピーを作成します。つまり、シリアル化されたパケット内のすべてのデータは、実際には完全なデータであり、ポインターはありません。serialized
このようにして、ソケットを介して単一のバッファ ( is と呼びます) を送信できます。単純にポインターを送信した場合、ポインターを受信したユーザーは、そのポインターが有効なメモリ アドレスであることを期待します。ただし、メモリ アドレスがまったく同じになる可能性はほとんどありません。ただし、たとえそうであったとしても、彼はそのアドレスであなたと同じデータを持っているわけではありません (非常に限られた特殊な状況を除いて)。
この点は、デシリアライゼーション プロセス (これは受信者側にあります) を見ることでより明確になることを願っています。送信者から送信された情報を保持する構造体をどのように割り当てているかに注目してください。送信者が完全な文字列ではなくメモリ アドレスのみを送信した場合、送信されたデータを実際に再構築できませんでした (同じマシン上でも、同じではない 2 つの異なる仮想メモリ スペースがあります)。したがって、本質的に、ポインターはオリジネーターにとって適切なマッピングにすぎません。
最後に、「構造体内の構造体」に関する限り、構造体ごとにいくつかの関数が必要になります。とはいえ、関数を再利用できる可能性はあります。たとえば、2 つの構造体がA
あり、B
where がA
含まれている場合、 B
2 つのシリアル化メソッドを使用できます。
char* serializeB()
{
// ... Do serialization
}
char* serializeA()
{
char* B = serializeB();
// ... Either add on to serialized version of B or do some other modifications to combine the structures
}
したがって、構造体ごとに 1 つのシリアル化メソッドで問題を解決できるはずです。