web-dev-qa-db-ja.com

C-シリアル化手法

ネットワーク上で送信するデータをシリアル化するコードを書いています。現在、私はこの原始的な手順を使用しています:

  1. void*バッファーを作成します
  2. ネットワーク経由で送信するデータにhtonファミリなどのバイト順序操作を適用します
  3. memcpyを使用して、メモリをバッファにコピーします
  4. ネットワーク経由でメモリを送信します

問題は、さまざまなデータ構造(多くの場合void *データが含まれているため、バイト順を気にする必要があるかどうかわからない)で、コードがveryそれぞれに固有のシリアル化コードで本当に肥大化することですデータ構造であり、まったく再利用できません。

これを簡単にする/ lessくしないCの優れたシリアル化手法は何ですか?

-

注:特定のプロトコルにバインドされているため、データをシリアル化する方法を自由に選択できません。

31
ryyst

各データ構造に対して、Xへのポインターと不透明なバッファー構造へのポインターを受け取り、適切なシリアル化関数を呼び出すserialize_X関数(Xは構造体名)を用意します。バッファーに書き込み、出力インデックスを更新するserialize_intなどのいくつかのプリミティブを指定する必要があります。プリミティブは、reserve_space(N)のようなものを呼び出す必要があります。Nは、データを書き込む前に必要なバイト数です。 reserve_space()は、void *バッファーを再割り当てして、少なくとも現在のサイズにNバイトを加えたサイズ以上にします。これを可能にするには、バッファ構造に、実際のデータへのポインタ、次のバイトを書き込むインデックス(出力インデックス)、およびデータに割り当てられたサイズを含める必要があります。このシステムでは、serialize_X関数はすべて、たとえば次のように非常に簡単です。

struct X {
    int n, m;
    char *string;
}

void serialize_X(struct X *x, struct Buffer *output) {
    serialize_int(x->n, output);
    serialize_int(x->m, output);
    serialize_string(x->string, output);
}

フレームワークのコードは次のようになります。

#define INITIAL_SIZE 32

struct Buffer {
    void *data;
    int next;
    size_t size;
}

struct Buffer *new_buffer() {
    struct Buffer *b = malloc(sizeof(Buffer));

    b->data = malloc(INITIAL_SIZE);
    b->size = INITIAL_SIZE;
    b->next = 0;

    return b;
}

void reserve_space(Buffer *b, size_t bytes) {
    if((b->next + bytes) > b->size) {
        /* double size to enforce O(lg N) reallocs */
        b->data = realloc(b->data, b->size * 2);
        b->size *= 2;
    }
}

これから、必要なすべてのserialize_()関数を実装するのは非常に簡単なはずです。

編集:例:

void serialize_int(int x, Buffer *b) {
    /* assume int == long; how can this be done better? */
    x = htonl(x);

    reserve_space(b, sizeof(int));

    memcpy(((char *)b->data) + b->next, &x, sizeof(int));
    b->next += sizeof(int);
}

編集:また、私のコードには潜在的なバグがあることに注意してください。バッファー配列のサイズはsize_tに格納されますが、インデックスはintです(size_tがインデックスの適切なタイプと見なされるかどうかはわかりません)。また、エラー処理のための準備がなく、完了後にバッファを解放する機能がないため、これを自分で行う必要があります。使用する基本的なアーキテクチャのデモを行っただけです。

34
jstanley

ライブラリを使用することをお勧めします。

私は既存のものに満足していなかったので、私たちの生活を楽にするために Binn ライブラリを作成しました。

以下に使用例を示します。

  binn *obj;

  // create a new object
  obj = binn_object();

  // add values to it
  binn_object_set_int32(obj, "id", 123);
  binn_object_set_str(obj, "name", "Samsung Galaxy Charger");
  binn_object_set_double(obj, "price", 12.50);
  binn_object_set_blob(obj, "picture", picptr, piclen);

  // send over the network
  send(sock, binn_ptr(obj), binn_size(obj));

  // release the buffer
  binn_free(obj);
5
Bernardo Ramos

シリアル化を自分で実装しようとしないでください。これは何十億回も行われているため、既存のソリューションを使用する必要があります。例えばprotobufs: https://github.com/protobuf-c/protobuf-c

また、他の多くのプログラミング言語と互換性があるという利点もあります。

4
Assaf Lavie

プロトコルの制約が何であるかを知っていれば役立ちますが、一般的に、オプションは非常に限られています。データが構造体ごとにバイト配列sizeof(struct)の和集合を作成できるようなものである場合、物事を単純化するかもしれませんが、説明からは、より本質的な問題があるように聞こえます:ポインターを転送する場合(言及void * data)これらのポイントが受信側のマシンで有効になる可能性は非常に低いです。データがメモリ内の同じ場所に表示されるのはなぜですか?

2
Charlie Martin