4

ネットワークパケットペイロードとして次のクラスがある場合:

class Payload {char field0; int field1; char field2; int field3; };

Payloadのようなクラスを使用すると、データの受信者は、ソケットを介してデータを受信するときにアライメントの問題の影響を受けやすくなりますか?クラスを並べ替えるか、配置を確実にするためにパディングを追加する必要があると思います。

どちらかの並べ替え:

class Payload
{
    int  field1;
    int  field3;
    char field0;
    char field2;
};

またはパディングを追加します:

class Payload
{
    char  field0;
    char  pad[3];
    int   field1;
    char  field2;
    char  pad[3];
    int   field3; 
};

なんらかの理由で並べ替えが意味をなさない場合は、クラスのサイズが大きくなっても配置の問題を回避できるため、パディングを追加することをお勧めします。

ネットワークデータにおけるこのようなアライメントの問題について、どのような経験がありますか?

4

6 に答える 6

8

正しく、やみくもに位置合わせを無視すると、問題が発生する可能性があります。同じオペレーティングシステムでも、2つのコンポーネントが異なるコンパイラまたは異なるコンパイラバージョンでコンパイルされている場合。


1)ある種のシリアル化プロセスを介してデータを渡します。
2)または、バイト順序に注意を払いながら、各プリミティブを個別に渡します==エンディアン

開始するのに適した場所は、BoostSerializationです。

于 2009-04-15T20:00:11.187 に答える
4

別のポスターが言ったように、Googleプロトコルバッファ、またはBoost::serializeを調べる必要があります。

あなたがあなた自身を転がしたいならば、それを正しくしてください。

stdint.hの型(例:uint32_t, int8_t,など)を使用し、すべての変数が「ネイティブアラインメント」を持っていることを確認する場合(つまり、そのアドレスはサイズで均等に分割可能です(int8_tsはどこにでもあり、uint16_tsは偶数アドレスにあり、uint32_tsはアドレスにあります)。 4)で割り切れるので、位置合わせやパッキングについて心配する必要はありません。

以前の仕事では、XMLで定義されたデータバス(イーサネットまたはCANbusまたはバイトフライトまたはシリアルポート)を介してすべての構造を送信していました。構造内の変数の配置を検証し(誰かが不正なXMLを書き込んだ場合にアラートを出す)、構造を送受信するためのさまざまなプラットフォームと言語のヘッダーファイルを生成するパーサーがありました。これは私たちにとって本当にうまくいきました、私たちは決してメッセージの解析やパッキングを行うために手書きのコードについて心配する必要があり、すべてのプラットフォームで愚かな小さなコーディングエラーが発生しないことが保証されました。一部のデータリンク層は帯域幅にかなり制約があったため、ビットフィールドなどを実装し、パーサーが各プラットフォームに適切なコードを生成しました。列挙型もありましたが、これは非常に便利でした(人間が列挙型のコーディングビットフィールドを手作業で台無しにするのがいかに簡単であるかに驚かれることでしょう)。

Cを使用する8051およびHC11で実行されること、または帯域幅が非常に制限されているデータリンク層で実行されることを心配する必要がない限り、プロトコルバッファよりも優れたものを思い付く必要はなく、多くの時間を試してみることになります。それらと同等であるために。

于 2009-04-15T20:24:26.683 に答える
4

今日、メモリ内のバイナリパケットに直接オーバーレイされるパック構造を使用しており、それを実行することを決定した日を失っています。これを機能させる唯一の方法は、次のとおりです。

  1. コンパイル環境に基づいてビット幅固有のタイプを慎重に定義する(typedef unsigned int uint32_t
  2. 適切なコンパイラ固有のプラグマを挿入して、構造体メンバーの密なパッキングを指定します
  3. すべてが1バイトの順序である必要があります(ネットワークまたはビッグエンディアンの順序を使用)
  4. サーバーとクライアントの両方のコードを注意深く書く

始めたばかりの場合は、構造物でワイヤー上にあるものを表現しようとする混乱全体をスキップすることをお勧めします。各プリミティブ要素を個別にシリアル化するだけです。Boost Serializeのような既存のライブラリやTibCoのようなミドルウェアを使用しないことを選択した場合は、シリアル化メソッドの詳細を隠すバイナリバッファの周りに抽象化を記述することで、多くの頭痛の種を節約できます。次のようなインターフェースを目指します。

class ByteBuffer {
public:
    ByteBuffer(uint8_t *bytes, size_t numBytes) {
        buffer_.assign(&bytes[0], &bytes[numBytes]);
    }
    void encode8Bits(uint8_t n);
    void encode16Bits(uint16_t n);
    //...
    void overwrite8BitsAt(unsigned offset, uint8_t n);
    void overwrite16BitsAt(unsigned offset, uint16_t n);
    //...
    void encodeString(std::string const& s);
    void encodeString(std::wstring const& s);

    uint8_t decode8BitsFrom(unsigned offset) const;
    uint16_t decode16BitsFrom(unsigned offset) const;
    //...
private:
    std::vector<uint8_t> buffer_;
};

パケットクラスには、aByteBufferにシリアル化するか、aByteBufferとoffsetから逆シリアル化するメソッドがあります。これは、私が過去に戻って修正できることを絶対に望んでいることの1つです。バイトの交換を忘れたり、をパックしなかったりしたために発生した問題のデバッグに時間を費やした回数を数えることができませんstruct

避けるべきもう1つのトラップは、unionバイトを表すためにaを使用するかmemcpy、バイトを抽出するためにunsignedcharバッファーにingを使用することです。常にビッグエンディアンをネットワークで使用している場合は、簡単なコードを使用してバイトをバッファーに書き込むことができ、内容について心配する必要はありませんhtonl

void ByteBuffer::encode8Bits(uint8_t n) {
    buffer_.push_back(n);
}
void ByteBuffer::encode16Bits(uint16_t n) {
    encode8Bits(uint8_t((n & 0xff00) >> 8));
    encode8Bits(uint8_t((n & 0x00ff)     ));
}
void ByteBuffer::encode32Bits(uint32_t n) {
    encode16Bits(uint16_t((n & 0xffff0000) >> 16));
    encode16Bits(uint16_t((n & 0x0000ffff)      ));
}
void ByteBuffer::encode64Bits(uint64_t n) {
    encode32Bits(uint32_t((n & 0xffffffff00000000) >> 32));
    encode32Bits(uint32_t((n & 0x00000000ffffffff)      ));
}

数値表現は常に論理的にビッグエンディアンであるため、これはプラットフォームにとらわれないままです。このコードは、プリミティブ型のサイズに基づいたテンプレートを使用するのにも非常に適しています(考えてみてくださいencode<sizeof(val)>((unsigned char const*)&val))...それほどきれいではありませんが、非常に簡単に記述および保守できます。

于 2009-04-15T21:17:20.393 に答える
2

私の経験では、次のアプローチが優先されます (優先順で)。

  1. Tibco、CORBA、DCOM など、これらすべての問題を管理する高レベルのフレームワークを使用してください。

  2. パッキング、バイト順、およびその他の問題を認識している接続の両側に独自のライブラリを記述します。

  3. 文字列データのみを使用して通信します。

仲介なしで生のバイナリ データを送信しようとすると、ほぼ確実に多くの問題が発生します。

于 2009-04-15T20:05:59.130 に答える
1

なんらかの移植性が必要な場合、これにクラスや構造を使用することは事実上できません。あなたの例では、int はシステムに応じて 32 ビットまたは 64 ビットの場合があります。ほとんどの場合、リトル エンディアンのマシンを使用していますが、古い Apple Mac はビッグ エンディアンです。コンパイラも好きなように自由にパディングできます。

一般に、n2hll、n2hl、または n2hs で正しいバイト順を取得した後、各フィールドを一度に 1 バイトずつバッファに書き込むメソッドが必要です。

于 2009-04-15T20:08:56.783 に答える
1

構造体に自然なアラインメントがない場合、コンパイラは通常、アラインメントが適切になるようにパディングを挿入します。ただし、プラグマを使用して構造体を「パック」する (パディングを削除する) と、非常に有害な副作用が発生する可能性があります。PowerPC では、アラインされていない float は例外を生成します。その例外を処理しない組み込みシステムで作業している場合は、リセットされます。その割り込みを処理するルーチンがある場合、コードの速度が大幅に低下する可能あります。これは、ソフトウェア ルーチンを使用してミスアライメントを回避し、パフォーマンスを静かに低下させるためです。

于 2009-04-15T21:58:03.587 に答える