blog

WebSocket技術の分析と応用

ajaxポーリング技術:クライアントは指定された間隔で毎回リクエストを送信し、サーバーに新しいデータを要求します。 ロングポーリング: クライアントはサーバーにリクエストを一度だけ送信し、サーバーがデ...

Apr 27, 2020 · 6 min. read
シェア

紹介

以前のHTTPプロトコルでは、インスタントメッセージのプッシュを実装したい場合、2つの方法しかありませんでした:

  • ajaxポーリング手法: クライアントは指定された間隔で毎回リクエストを送信し、サーバーに新しいデータを要求します。
  • ロング・ポーリング ロング・ポーリング手法: クライアントはサーバにリクエストを一度だけ送信し、サーバからデータが返されるまでペディング・ブロック状態を維持します。

これらの方法はどちらも非常にシンプルですが、唯一の欠点は、HTTP接続を確立し、サーバから受動的にデータを受け取ることしかできないということです。メッセージがプッシュされるのを待っている何千人ものユーザーがいて、突然サーバーが圧倒されてストライキを起こしたとしたら、恥ずかしいことです。

WebSocketは、HTTP5とその出現と一緒に、そのようなシナリオのために設計されており、実際には、HTTPプロトコルとは何の関係もありません、それはWebSocketは、クライアントとサーバーが全二重プロトコルに長い接続を行うための、ブランドの新しいと言うことができます。

WebSocketは、クライアントとサーバーが接続を確立するために一度だけ確立する必要がありますが、それはTCPプロトコルに基づいて構築されているため、それはまだ本質的に3つのハンドシェイクです。

TCP自体は持続的な接続であり、3回のハンドシェイクや4回のハンドシェイクは古くなりません。HTTPは単方向性で、サーバーはリクエストに応答するだけで、積極的にデータを送信することはできません。つまり、WebSocketはHTTPのパッチと言えます。

プロトコルの概要

WebSocketプロトコルはエクスプローラでこのように表示されます:

通常のリクエストとは異なり、WebSocket URL の先頭に ws が付いていることがわかります。これは HTTP リクエストではなく WebSocket リクエストであることをブラウザに伝えるもので、この時点でブラウザは自動的にプロトコルをアップグレードします。

デフォルトの ws ポートは次のとおりです。

wss は TLS で暗号化された ws です。

通常の HTTP リクエストとは異なり、WebSocket リクエストはアプリケーションにいくつかのフィールドを追加します:

  • Sec-WebSocket-AcceptSec-WebSocket-AcceptSec-WebSocket-Key: Sec-WebSocket-Key の値が固定アルゴリズムで暗号化され、応答ヘッダーの値が同じままであるときのみ、接続は確立されたと認識され、クロスプロトコル攻撃を回避します。
  • Sec-WebSocket-VersionSec-WebSocket-Version: このヘッダーフィールドの値は13でなければなりません。なぜなら、9、 10、11、12のような多くのテストされたバージョンが存在し、それらは現在 有効ではないと考えられているからです。
  • Sec-WebSocket-Extensions: この属性は、接続が確立されたときにサーバーが処理できるクライアント側の 拡張を格納します。
  • Upgrade: この HTTP プロトコルが WebSocket にアップグレードされたことを伝えます。

クライアントがサーバに対して WebSocket リクエストを開始するとき、現在の接続がすでに確立されている場合にのみ、接続を再確立できます。WebSocket は長い接続であるため、クライアントは大量の WebSocket 接続を作成するスクリプトによる DDOS 攻撃を避けるために、同じホストへの接続数を制限するよう注意する必要があります。クライアントがプロキシ経由でサービスにアクセスしている場合、クライアントはそのプロキシに接続し、そのプロキシ経由でサーバとの TCP 接続を確立する必要があります。

Sec-WebSocket-VersionSec-WebSocket-Extensionsサーバはクライアントの WebSocket リクエストを受信すると、リクエストを解析して Sec-WebSocket-Key、クライアントの送信元アドレス、要求されたリソースの名前などを取得する必要があります。Sec-WebSocket-Accept構文解析が完了すると、サーバに接続できる場合、サーバはクライアントに応答を返し、応答にはクライアント接続の識別子である , が含まれます。

クライアントとサーバーが接続を確立すると、両者は通信できるようになります:

プロトコルの構造

WebSocket プロトコルのグローバル構造は、各フィールドの意味を大まかに解析すると以下のようになります:

  • FIN: これがメッセージの最後のフィールドであることを示します。

  • RESV1/RESV2/RESV3:拡張プロトコルがあるかどうかを識別します。これが 1 の場合、EXTEND PAYLOAD が 0 であれば、WebSocket 接続は切断されます。

  • OPCODE: WebSocket のアクションを示すアクションフレームであるオペコードを識別します。デフォルトの識別コードは以下のとおりです:

    • x0 : 連続メッセージ・スライス
    • x1 : テキスト・ベースのメッセージ・スライス。
    • x2 : バイナリ・タイプのメッセージ・スライス。
    • x3-7 : 後で使用するために予約されたデータ・フレーム。
    • x8 : 接続を閉じるコマンド。
    • %x9 pingパケット
    • xA: pong パケット
    • xB-F: 後で使うために予約された制御フレーム。

    xB-F:後で使用するために予約されている制御フレーム。

  • MASK: データがマスクされているかどうかを識別します。1 に設定されている場合、マスク・キーは MASKING KEY 領域に配置する必要があります。

  • PAYED LENGTH:送信データの長さ。

  • MASKING-KEY: マスキングキー。

    • EXTENDED DATA:自分で定義した拡張プロトコル。
    • APPLICATION DATA:ベース・データ・フレーム。

フロントエンド処理

HTML5は処理をカプセル化し、APIを呼び出すだけです。

WebSocket オブジェクトを直接作成し、onopenonclose などのメソッドをオブジェクトにバインドします。

関連する API はMDN WEB - API WebSocketで見ることができます。

バックエンド処理

WebSocketServerProtocolHandlerNetty を例に挙げると、WebSocket サービスを作成する場合、プロトコルの内容を使いやすいラッパークラスにカプセル化する WebSocket プロトコル用のプロセッサを含めることは避けられません:

WebSocketServerProtocolHandlerこのクラスに入ると、プロパティが定義されていることがわかります:

メソッドは 2 つあります:

WebSocketServerProtocolHandshakeHandlerひとつは handlerAdded で、チャネルが接続されるたびにコールバックされます。

1つはdecodeで、実行されたデータ・フレームを操作します。

closeの前にframe.retrieve();を実行するのは、フレームがcloseに必要だからであり、すべてのNetty操作が非同期であるため、フレームが使い切られずに解放されるのを防ぐためです。

メソッドを見てみましょう:

WebSocketServerProtocolHandshakeHandler.obj;でトリガーイベントをバインドする際、互換性を保つために2つ設定されています。

WebSocketServerProtocolHandshakeHandler.foo;Sec-WebSocket-Acceptハンドシェイカーファクトリーを通して作成された、ハンドシェイクメソッドに注目する必要があります。このメソッドは、実際には応答データを送信するためにあります。

まず HTTP の集約と圧縮のためのプロセッサを削除し、次に HttpRequestDecoder があるかどうかを調べ、なければ WebSocket コーデックをその前に追加します。HTTP コーデックがある場合は、コーデックを WebSocket コーデックに置き換え、レスポンスの送信が成功したら、HttpServerCodec または HttpResponseEncoder を削除します。

この処理の後、HTTPコーデックが削除されます。このようにすることで、ユーザーが間違ったプロセッサを追加しても、プログラムがWebSocket接続を正常に実行できるようになります。

まとめ

WebSocketプロトコルは、長い接続を介してデータを転送するために使用されますが、本質はプロトコル形式を定義し、その中にデータを置くことです。機能的には、HTTPとWebSocketの唯一の違いは、クライアントとサーバーが受動的ではなく、お互いにメッセージをプッシュできることです。

HTTP 1.1 では keep-alive リクエストヘッダ属性が追加されました。keep-alive の役割は接続を維持することで、他の HTTP リクエストがチャネルを再利用できるようにします。各 HTTP リクエストはリクエストヘッダを保持しますが、WebSocket のロングコネクションでは各コネクションに 1 つのクライアントが存在します。

わかりやすい例えに、カスタマーサービスに電話をかけるとします。キープアライブとは、一方の当事者が話し終えてから後ろの人に電話を渡し、後ろの人がカスタマーサービスに新しい問題を反映させることを意味します。WebSocketとは、一方の当事者が話し終え、カスタマーサービスからのフィードバックを聞いてから電話を切ることを意味し、2人は切断されます。

WebSocketプロトコルの詳細を理解したい場合は、ここにお勧めの記事です:

Read next

flinkチュートリアル - JDBCカタログのflink 1.11の説明

1.11.0では、リレーショナルデータベースの読み書きや変更履歴の読み取りにFlinkを利用しているユーザは、対応するスキーマを手動で作成しなければなりませんが、データベースのスキーマが変更されると、対応するFlinkタスクも手動で更新して型の一致を保つ必要があるという問題があります。

Apr 27, 2020 · 5 min read