1

私は、ソケットを介してそれ自体の別のインスタンスとデータを送受信するアプリケーションに取り組んでおり、「END」タグでデータをカプセル化する最も効率的な方法に興味があります。たとえば、ソケット接続を介した読み取りと書き込みに使用される 2 つの関数を次に示します。

def sockWrite(conn, data):
    data = data + ":::END"
    conn.write(data)

def sockRead(conn):
    data = ""
    recvdata = conn.read()
    while recvdata:
        data = data + recvdata
        if data.endswith(':::END'):
            data = data[:len(data)-6]
            break
        recvdata = conn.read()
    if data == "":
        print 'SOCKR: No data')
    else:
        print 'SOCKR: %s', data)
    return data

この単一の書き込みに対して複数の読み取りが発生する可能性があるため、基本的に「:::END」を書き込みに追加しています。したがって、読み取りは「:::END」に達するまでループします。

これはもちろん、データ変数に文字列 ":::END" が含まれている場合に問題を引き起こします。これはたまたま読み取りの最後に来ます。

帯域幅の追加を最小限に抑えてデータをカプセル化する適切な方法はありますか? pickle または json について考えていましたが、バイナリ データが ASCII に変換されると信じているため、かなりの量の帯域幅が追加されるのではないかと心配していました。私はそれで正しいですか?

ありがとう、ベン

4

1 に答える 1

1

ゼロ:本当にこれを最適化する必要がありますか?

通常、比較的小さなメッセージを送信します。512バイトのメッセージから60バイトを削減することは、無視しているイーサネット、IP、TCPのオーバーヘッド、および帯域幅を圧倒するRTTを見ると、通常はばかげています。

一方、巨大なメッセージ送信する場合、同じ接続で複数のメッセージを送信する必要がないことがよくあります。

HTTP、IMAPなどの一般的なインターネットプロトコルを見てください。それらのほとんどは、行で区切られ、人間が読める形式で、簡単にデバッグできるプレーンテキストを使用しています。HTTPは「残りのメッセージ」をバイナリで送信できますが、送信が終了したらソケットを閉じます。

99%の場合、これで十分です。あなたのケースでそれが十分ではないと思うなら、私はまだあなたのプロトコルのテキストバージョンを書き、そしてあなたがすべてをデバッグして動作させたらオプションのバイナリバージョンを追加します(そしてそれが本当にあるかどうかをテストします違いを生む)。


一方、コードには2つの問題があります。

まず、":::END"ご存知のように、区切り文字として使用していて、メッセージにその文字列をデータに含めることができる場合は、あいまいさがあります。この問題を解決する通常の方法は、何らかの形でエスケープまたは引用することです。本当に簡単な例:

def sockWrite(conn, data):
    data = data.replace(':', r'\:') + ":::END"
    conn.write(data)

読み取り側では、区切り文字を削除してからreplace('r\:', ':')、メッセージを削除します。(もちろん、6バイトの':::END'区切り文字を使用するためだけにすべてのコロンをエスケープするのは無駄です。エスケープされていないコロンを区切り文字として使用するか、より複雑なエスケープメカニズムを作成することもできます。)

次に、「この1回の書き込みで複数の読み取りが発生する可能性がある」というのは正しいですが、この1回の読み取りで複数の書き込みが発生する可能性があることも事実です。このメッセージの半分と次のメッセージの半分を読むことができます。これは、あなたがただ使うことができないことを意味しますendswith; partitionまたはのようなものを使用しsplit、複数のメッセージを処理できるコードを記述し、次にreadループを通過するまで部分的なメッセージを格納できるコードを記述しなければなりません。


その間、あなたの特定の質問に:

帯域幅の追加を最小限に抑えてデータをカプセル化する適切な方法はありますか?

確かに、少なくとも3つの適切な方法があります。区切り文字、プレフィックス、または自己区切り形式です。

あなたはすでに最初のものを見つけました。そして、それに関する問題:データに表示されない可能性のある文字列(たとえば、'\0'人間が読めるUTF-8テキスト)がない限り、エスケープを必要としない区切り文字を選択することはできません。

JSONのような自己区切り形式が最も簡単なソリューションです。最後に開いたブレース/ブラケットが閉じると、メッセージは終了し、次のメッセージの時間です。

または、各メッセージの前に長さを含むヘッダーを付けることもできます。これは、多くの低レベルプロトコル(TCPなど)が行うことです。このための最も単純な形式の1つは、netstringです。ここで、ヘッダーは、通常の基数10の文字列として表される整数としてのバイト単位の長さであり、その後にコロンが続きます。netstringプロトコルは、区切り文字としてコンマ使用します。これにより、エラーチェックが追加されます。


私はpickleまたはjsonについて考えていましたが、バイナリデータをASCIIに変換すると信じているため、かなりの帯域幅が追加されるのではないかと心配していました。

pickleバイナリ形式とテキスト形式の両方があります。ドキュメントで説明されているように、プロトコル、、、またはを使用する23HIGHEST_PROTOCOLかなり効率的なバイナリ形式が得られます。

一方、JSONは、文字列、数値、配列、および辞書のみを処理します。JSONエンコードする前に、バイナリデータを手動で文字列(または文字列や数値の配列など)にレンダリングしてから、反対側で逆にする必要があります。これを行う2つの一般的な方法は、base-64とhexです。これらは、データのサイズにそれぞれ25%と100%を追加しますが、本当に必要な場合は、より効率的な方法があります。

そしてもちろん、JSONプロトコル自体は、厳密に必要な文字よりも数文字多く使用します。これらすべての引用符やコンマなどを使用すると、フィールドに付けた名前はすべて非圧縮UTF-8として送信されます。JSONは、 BSONProtocol BuffersXDR、またはそれが本当に問題である場合は「無駄」が少ない他のシリアル化形式にいつでも置き換えることができます。

一方、pickle自己区切りではありません。メッセージの選択を解除する前に、まずメッセージを分割する必要があります。JSON自己区切りですが、json.loads最初にメッセージを分割しない限り、単に使用することはできません。もっと複雑なものを書く必要があります。最も簡単に機能するのはraw_decode、オブジェクトを取得するまでバッファを繰り返し呼び出すことです。

于 2013-03-06T22:48:29.487 に答える