1. ホーム
  2. encoding

[解決済み] サーバー側でWebSocketメッセージを送受信するにはどうすればよいですか?

2023-04-03 06:42:31

質問

  • WebSocket を使用して、プロトコルに従ってサーバー側でメッセージを送受信するにはどうすればよいですか。

  • ブラウザからサーバーにデータを送信するとき、なぜサーバーで一見ランダムなバイトが得られるのでしょうか?データは何らかの方法でエンコードされているのでしょうか?

  • サーバー→クライアント、クライアント→サーバーの両方向で、フレーミングはどのように行われるのでしょうか?

どのように解決するのですか?

注: これは、決定的なフレーミング形式に従って WebSocket メッセージの受信と送信を処理できる、非常につまらないサーバーを実装する方法に関する説明と擬似コードです。これには、ハンドシェーキング プロセスは含まれていません。さらに、この回答は教育目的のために作成されたものであり、全機能を備えた実装ではありません。

仕様書 (RFC 6455)


メッセージの送信

(言い換えれば、サーバー → ブラウザ)

送信するフレームは、WebSocket のフレーム形式に従ってフォーマットされる必要があります。メッセージを送信する場合、この形式は次のようになります。

  • データの種類を含む 1 バイト (および些細なサーバーの範囲外であるいくつかの追加情報)
  • 長さを含む1バイト
  • 長さが2バイト目に収まらない場合は、2または8バイト(2バイト目は、長さに何バイトが使用されるかを示すコードとなります)
  • 実際の (生の) データ

最初のバイトは 1000 0001 (または 129 ) を使ってテキストフレームを作ることができます。

2バイト目は最初のビットが 0 に設定されています (サーバーからクライアントへのエンコーディングは必須ではありません)。

長さバイトを正しく送信するために、生データの長さを決定することが必要です。

  • もし 0 <= length <= 125 の場合、追加のバイトは必要ありません。
  • もし 126 <= length <= 65535 の場合、さらに2バイトが必要で、2バイト目は 126
  • もし length >= 65536 の場合、さらに8バイトが必要で、2バイト目は 127

長さは別々のバイトにスライスされなければなりません。つまり、(8ビットの量で)右にビットシフトし、最後の8ビットだけを保持するために、次のようにする必要があるということです。 AND 1111 1111 (となります)。 255 ).

長さのバイトの後に、生データが来ます。

これは次のような疑似コードになります。

bytesFormatted[0] = 129

indexStartRawData = -1 // it doesn't matter what value is
                       // set here - it will be set now:

if bytesRaw.length <= 125
    bytesFormatted[1] = bytesRaw.length

    indexStartRawData = 2

else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
    bytesFormatted[1] = 126
    bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
    bytesFormatted[3] = ( bytesRaw.length      ) AND 255

    indexStartRawData = 4

else
    bytesFormatted[1] = 127
    bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
    bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
    bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
    bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
    bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
    bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
    bytesFormatted[8] = ( bytesRaw.length >>  8 ) AND 255
    bytesFormatted[9] = ( bytesRaw.length       ) AND 255

    indexStartRawData = 10

// put raw data at the correct index
bytesFormatted.put(bytesRaw, indexStartRawData)


// now send bytesFormatted (e.g. write it to the socket stream)


メッセージの受信

(つまり、ブラウザ→サーバ)

取得するフレームは以下のような形式です。

  • データの種類を含む1バイト
  • 長さを表す1バイト
  • 長さが2バイト目に収まらない場合は、2バイトまたは8バイトを追加する
  • 4バイトはマスク(=デコードキー)です。
  • 実際のデータ

最初のバイトは通常重要ではありません。もしテキストを送信するだけであれば、テキストタイプのみを使用することになります。それは次のようになります。 1000 0001 (または 129 ) を使用することができます。

2 バイト目と追加の 2 または 8 バイト目は何らかの解析が必要です。なぜなら、長さに何バイトが使われているかを知る必要があるからです(実際のデータがどこから始まるかを知る必要があります)。長さそのものは、すでにデータを持っているので、通常は必要ありません。

2 バイト目の最初のビットは常に 1 となっており、データがマスクされている(=エンコードされている)ことを意味します。クライアントからサーバーへのメッセージは常にマスクされています。この最初のビットを削除するために secondByte AND 0111 1111 . 結果として得られるバイトが2バイト目に収まらなかったために長さを表さないというケースが2つあります。

  • の2バイト目が 0111 1110 または 126 は、次の2バイトが長さに使用されることを意味します。
  • の2バイトを 0111 1111 または 127 は、次の8バイトが長さに使用されることを意味します。

4つのマスクバイトは、実際に送信されたデータをデコードするために使用されます。デコードのアルゴリズムは次のとおりです。

decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]

ここで encodedByte はデータの元バイトです。 encodedByteIndex は最初のバイトから数えたバイトのインデックス(オフセット)です。 は実データの であり、インデックス 0 . masks は4つのマスクバイトを含む配列です。

これは次のようなデコードのための疑似コードにつながります。

secondByte = bytes[1]

length = secondByte AND 127 // may not be the actual length in the two special cases

indexFirstMask = 2          // if not a special case

if length == 126            // if a special case, change indexFirstMask
    indexFirstMask = 4

else if length == 127       // ditto
    indexFirstMask = 10

masks = bytes.slice(indexFirstMask, 4) // four bytes starting from indexFirstMask

indexFirstDataByte = indexFirstMask + 4 // four bytes further

decoded = new array

decoded.length = bytes.length - indexFirstDataByte // length of real data

for i = indexFirstDataByte, j = 0; i < bytes.length; i++, j++
    decoded[j] = bytes[i] XOR masks[j MOD 4]


// now use "decoded" to interpret the received data