5

私は最近、Stack Overflowで、16ビット整数からデータをキャストし、その後に未定の量のvoid*キャストメモリを既知のソケットライブラリを使用するためにunsignedcharsのstd::vectorにキャストする方法について質問しました。署名が次のような関数を使用して生データを送信するNetLinkとして:

void rawSend(const vector<unsigned char>* data);

(参考のために、ここにその質問があります:unsigned int +文字列をunsignedcharベクトルにキャストします

質問への回答に成功し、回答してくださった方々に感謝いたします。Mike DeSimoneは、データをNetLinkが受け入れる形式(std :: vector)に変換するsend_message()関数の例で応答しました。これは次のようになります。

void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize)
{
    vector<unsigned char> buffer;
    buffer.reserve(sizeof(uint16_t) + rawDataSize);
    buffer.push_back(opcode >> 8);
    buffer.push_back(opcode & 0xFF);
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
    buffer.insert(buffer.end(), base, base + rawDataSize);
    socket->rawSend(&buffer);
}

これはまさに私が必要としていたもののように見えるので、付随するreceive_message()関数の作成に着手しました...

...しかし、私はすべてのビットシフトなどを完全には理解していないと言うのが恥ずかしいので、ここで壁にぶつかりました。私が過去10年近くに書いたすべてのコードでは、ほとんどのコードは高級言語で書かれており、残りのコードは実際には低レベルのメモリ操作を要求していません。

receive_message()関数の記述に戻ると、ご想像のとおり、私の出発点はNetLinkのrawRead()関数であり、その署名は次のようになります。

vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL);

私のコードは次のように始まります。

void receive_message(NLSocket* socket, uint16_t* opcode, const void** rawData)
{
    std::vector<unsigned char, std::allocator<unsigned char>>* buffer = socket->rawRead();
    std::allocator<unsigned char> allocator = buffer->get_allocator(); // do I even need this allocator?  I saw that one is returned as part of the above object, but...
    // ...
}

rawRead()を最初に呼び出した後、ベクターを反復処理し、ベクターからデータを取得してビットシフト操作を元に戻し、データを*rawDataと*opcodeに返す必要があるようです。繰り返しになりますが、私はビットシフトにあまり精通していません(構文を理解するためにグーグルを実行しましたが、上記のsend_message()コードがシフトを必要とする理由をまったく理解していません)。ここ。

誰かがこの付随するreceive_message()関数の書き方を理解するのを手伝ってもらえますか?ボーナスとして、誰かが元のコードを説明して、将来的にそれがどのように機能するか(特に、この場合のシフトがどのように機能し、なぜそれが必要なのか)を知ることができれば、それは将来の私の理解を深めるのに役立ちます。

前もって感謝します!

4

2 に答える 2

3

ライブラリの関数シグネチャ…

    void rawSend( const vector<unsigned char>* data );

データの構築を強制しますstd::vector。これは、本質的に、不必要な非効率性を課すことを意味します。を構築するためにクライアントコードを要求することに利点はありませんstd::vector。彼らが何をしているのかわからないように設計した人は誰でも、彼らのソフトウェアを使わないのが賢明でしょう。

ライブラリ関数のシグネチャ…

    vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL);

std::stringさらに悪いことに、「hostFrom」(実際に意味するものは何でも)を指定する場合は、不必要にビルドする必要があるだけでなく、結果の割り当てを解除する必要がありますvector。少なくとも、結果タイプを機能させる意味がある場合。もちろん、これはないかもしれません。

非常に嫌な関数シグネチャを持つライブラリを使用するべきではありません。おそらく、ランダムに選択されたライブラリの方がはるかに優れています。つまり、はるかに使いやすいです。


どのように既存の使用法コード…

void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize)
{
    vector<unsigned char> buffer;
    buffer.reserve(sizeof(uint16_t) + rawDataSize);
    buffer.push_back(opcode >> 8);
    buffer.push_back(opcode & 0xFF);
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
    buffer.insert(buffer.end(), base, base + rawDataSize);
    socket->rawSend(&buffer);
}

作品:

  • このreserve呼び出しは、時期尚早の最適化の場合です。vectorおそらく2つ以上ではなく、(この時点で実行される)単一のバッファー割り当てのみを実行しようとします。を構築することの明白な非効率性に対するはるかに良い治療法はvector、より健全なライブラリを使用することです。

  • ベクトルの先頭に、(buffer.push_back(opcode >> 8)想定される)16ビット量の上位8ビットを配置します。opcode高い部分、最も重要な部分を最初に配置することは、ビッグエンディアン形式として知られています。もう一方の端の読み取りコードは、ビッグエンディアン形式である必要があります。同様に、この送信コードがリトルエンディアン形式を使用している場合、読み取りコードはリトルエンディアン形式を想定する必要があります。したがって、これは単なるデータ形式の決定ですが、決定を考えると、両端のコードはそれに準拠する必要があります。

  • buffer.push_back(opcode & 0xFF)呼び出しは、ビッグエンディアンの場合と同様に、上位ビットの後に下位8ビットを配置しますopcode

  • 宣言はconst unsigned char* base(reinterpret_cast<const unsigned char*>(rawData))、データへの適切に型指定されたポインターを指定するだけで、それを呼び出しますbase。このタイプは、バイトレベルのアドレス演算const unsigned char*を可能にするため適切です。元の仮引数タイプは、アドレス演算を許可していません。const void*

  • buffer.insert(buffer.end(), base, base + rawDataSize)、データをベクトルに追加します。式base + rawDataSizeは、前の宣言で有効にされたアドレス演算です。

  • socket->rawSend(&buffer)SillyLibraryのrawSendメソッドへの最後の呼び出しです。


SillyLibraryrawRead関数への呼び出しをラップする方法。

まず、バイトデータ型の名前を定義します(常に名前を付けることをお勧めします)。

typedef unsigned char Byte;
typedef ptrdiff_t Size;

SillyLibrary関数の結果の割り当て解除/破棄/削除(必要な場合)の方法については、ドキュメントを参照してください。

void deleteSillyLibVector( vector<Byte> const* p )
{
    // perhaps just "delete p", but it depends on the SillyLibrary
}

さて、送信操作にstd::vector関与したのはただの苦痛でした。受信操作の場合は逆です。動的配列を作成し、それを関数の結果として安全かつ効率的に渡すことは、まさにそのstd::vectorために設計されたものです。

ただし、送信操作は1回の呼び出しでした。

受信操作の場合 SillyLibraryの設計によっては、すべてのデータを受信するまで、ループする必要のある受信呼び出しの数を実行することができます。これを行うのに十分な情報を提供していません。ただし、以下のコードは、ループコードが呼び出すことができる最下層の読み取りvectorを示しており、データを次のように蓄積します。

Size receive_append( NLSocket& socket, vector<Byte>& data )
{
    vector<Byte> const* const result = socket.raw_read();

    if( result == 0 )
    {
        return 0;
    }

    struct ScopeGuard
    {
        vector<Byte>* pDoomed;
        explicit ScopeGuard( vector<Byte>* p ): pDoomed( p ) {}
        ~ScopeGuard() { deleteSillyLibVector( pDoomed ); }
    };

    Size const nBytesRead = result->size();
    ScopeGuard cleanup( result );

    data.insert( data.end(), result->begin(), result->end() );
    return nBytesRead;
}

クリーンアップを行うためにデストラクタを使用していることに注意してください。これにより、この例外がより安全になります。この特定のケースでは、考えられる唯一の例外は、std::bad_allocとにかくかなり致命的なです。ただし、例外の安全性のために、デストラクタを使用してクリーンアップを行う一般的な手法は、当然のこととして知って使用する価値があります(通常、新しいクラスを定義する必要はありませんが、SillyLibraryを扱う場合はそれをしなければならないかもしれません)。

最後に、ループコードがすべてのデータが手元にあると判断すると、のデータを解釈できますvector。それは主にあなたが求めていたものですが、私はそれを演習として残します。そして、それは私がすでにほぼ全体の記事のようにここに書いたからです。

免責事項:すぐに使えるコード。

乾杯&hth。、

于 2011-10-15T04:59:04.967 に答える
0

ビットをいじるのをビットをいじらない用語に入れると、はとopcode >> 8同等でopcode / 256あり、opcode & 0xFFと同等opcode - ((opcode / 256) * 256)です。丸め/切り捨てに注意してください。

それぞれが0..255の値を持つopcode2つのチャンクとで構成されているとophi考えてください。。oploopcode == (ophi * 256) + oplo

いくつかの追加の手がかり...

0xFF  == 255 == binary  11111111 == 2^8 - 1
0x100 == 256 == binary 100000000 == 2^8

              opcode
         /              \
Binary : 1010101010101010
         \      /\      /
           ophi    oplo

この理由は、基本的に、16ビット値をバイト単位のデータストリームに書き込むためのエンディアン修正です。ネットワークストリームには、特定のプラットフォームでのデフォルトの処理方法に関係なく、値の「ビッグエンド」を最初に送信する必要があるという独自のルールがあります。そのsend_messageは、基本的に16ビット値を分解して送信します。2つのチャンクを読み込んでから、16ビット値を再構築する必要があります。

再構成をコーディングするかどうかopcode = (ophi * 256) + oplo;opcode == (ophi << 8) | oplo;、ほとんどの場合好みの問題です。オプティマイザーは同等性を理解し、とにかく最も効率的なものを見つけます。

また、いいえ、アロケータは必要ないと思います。vectorパラメータを使用していることを考えると、使用するのが良いかどうかさえわかりませんconst void** rawDataが、おそらくそうなので、reserve読む前に行う必要があります。次に、関連するチャンク(オペコードを再構築するための2バイトと配列コンテンツ)を追加します。

私が見る大きな問題-あなたが読むことになる生データのサイズをどうやって知るのですか?のパラメータでreceive_messageはなく、データストリーム自体によって提供されるものでもないようです。

于 2011-10-15T04:28:19.210 に答える