WebSocket
クライアントとサーバー間で双方向のリアルタイム通信を実現するプロトコル
WebSocket とは
WebSocket は、HTTP のハンドシェイクで接続を確立した後、クライアントとサーバー間で双方向のリアルタイム通信を行うプロトコル (RFC 6455) である。HTTP のリクエスト/レスポンスモデルと異なり、サーバーからクライアントへ能動的にデータを送信できる。
HTTP との比較
HTTP との主な違いを以下に比較する。
| 観点 | HTTP | WebSocket |
|---|---|---|
| 方向 | リクエスト/レスポンス | 双方向 |
| 接続 | リクエストごとに開閉 | 持続的 |
| オーバーヘッド | ヘッダーが毎回付く | ハンドシェイク後はフレームごとの数バイト |
| プロトコル | http:// / https:// | ws:// / wss:// |
| 用途 | REST API、Web ページ | チャット、ゲーム、通知 |
ハンドシェイク後もオーバーヘッドがゼロになるわけではない。フレームには最小 2 バイトのヘッダーが付き、ペイロードが 126 バイト以上なら長さフィールドが伸びる。加えて RFC 6455 はクライアントから送るフレームすべてにマスク処理を義務付けているため、4 バイトのマスキングキーも毎回乗る。効いているのは、Cookie や認証ヘッダーのように毎回数百バイト単位で付いてくる情報を送り直さずに済む点である。
ハンドシェイク
接続は普通の HTTP リクエストとして始まり、サーバーが 101 Switching Protocols を返した時点で、同じ TCP 接続がそのまま WebSocket に切り替わる。専用のポートを開けずに済み、既存のファイアウォールや TLS の設定を流用できるのはこの作りのためである。
クライアント → HTTP Upgrade リクエスト → サーバー
GET /chat HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
サーバー → 101 Switching Protocols → クライアント
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
→ 以降は WebSocket フレームで双方向通信
Sec-WebSocket-Accept の値は、クライアントが送った Sec-WebSocket-Key に RFC 6455 が定める固定の識別子 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 を連結し、SHA-1 を取って Base64 にしたものである。鍵の交換ではなく、応答してきた相手が WebSocket を解していることの確認に使う。RFC はこの識別子を「WebSocket を理解しないネットワーク機器が使うとは考えにくい文字列」として選んでおり、Upgrade を無視したサーバーや途中のキャッシュの応答を、確立成功と誤認する事故を弾く役目を持つ。認証には関与しないので、利用者の識別は別に用意する。
Sec-WebSocket-Version: 13 が RFC 6455 の版を指す。ハンドシェイクが HTTP なので Cookie や TLS はそのまま効き、wss:// は既定で 443 番を通る。一方 HTTP/2 と HTTP/3 には Upgrade ヘッダーが無いため、この手順は使えない。HTTP/2 上では RFC 8441、HTTP/3 上では RFC 9220 が定める拡張 CONNECT で確立し、サーバーが SETTINGS_ENABLE_CONNECT_PROTOCOL を通知していることが前提になる。
クライアント側
ブラウザー側は WebSocket オブジェクトを作るだけで、ハンドシェイクは自動で進む。
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'subscribe', channel: 'notifications' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onclose = (event) => {
console.log(`Closed: ${event.code} ${event.reason}`);
// 再接続ロジック
};
つまずきやすい点が 2 つある。1 つは送信のタイミングで、readyState が CONNECTING のまま send() を呼ぶと例外になる。上の例のように onopen の中で送るか、readyState が OPEN であることを確かめてから送る。もう 1 つは認証で、ブラウザーのコンストラクターは接続先 URL とサブプロトコル名しか受け取らないため、Authorization のような独自ヘッダーを付けられない。ハンドシェイク時に送られる Cookie を使うか、トークンをサブプロトコル名やクエリ文字列に載せるか、接続後の最初のメッセージで認証を済ませるまで購読を受け付けない作りにするかを選ぶことになる。
SSE との使い分け
分かれ目は、クライアントからも送る必要があるかどうかである。SSE も接続を張り続ける方式だが、流れる向きがサーバーからクライアントへの一方向に限られる。
| 用途 | 選ぶ方式 | 理由 |
|---|---|---|
| チャットやゲームで双方向にやり取りする | WebSocket | 同じ接続でクライアントからも送信できる。サーバーからの一方向しか流れない方式では、操作を送るための経路を別に用意することになる |
| 通知やログをサーバーから流すだけ | SSE | 受け取り専用なら双方向の持続接続は要らず、ws:// への切り替えを挟まず HTTP のままで扱える |
| ダッシュボードの更新 | 送信方向で決める | 数値を表示し直すだけなら SSE で足りる。画面から購読するチャンネルを切り替えるなら、クライアント発の送信ができる WebSocket にする |
そもそも持続接続を張れない環境 (途中の機器が接続を維持してくれない場合など) では ロングポーリング が代替になる。相手がブラウザーではなくサーバーなら、接続を保たずイベントごとに HTTP を投げる Webhook の方が運用は軽い。
再接続の実装
持続接続は必ず切れる前提で組む。途中のプロキシやロードバランサーは無通信のまま一定時間が過ぎた接続を落とし、モバイル回線では回線の切り替えで切断され、サーバーをデプロイすれば全接続がまとめて閉じる。
function connect(url: string) {
let delay = 1000;
let closedByApp = false;
const open = (): WebSocket => {
const ws = new WebSocket(url);
ws.onopen = () => {
delay = 1000; // つながったら待ち時間を戻す
};
ws.onclose = () => {
if (closedByApp) return; // 自分で閉じたときは再接続しない
const wait = delay + Math.random() * 1000; // 一斉再接続を避けるためずらす
delay = Math.min(delay * 2, 30000);
setTimeout(open, wait);
};
return ws;
};
return { socket: open(), close: () => { closedByApp = true; } };
}
素朴に書くと踏みやすい落とし穴が 3 つある。1 つ目は、待ち時間を接続成功時に戻し忘れることである。上限まで伸びたままにすると、しばらく安定して動いた後に一度切れただけで、復帰が 30 秒後になる。2 つ目は、待ち時間を全クライアントで揃えてしまうことである。サーバーの再起動で一斉に切れた接続が同じ秒数後にまとめて戻り、立ち上がったばかりのサーバーを再び倒す。乱数でずらす。3 つ目は、自分で閉じた場合まで再接続してしまうことである。画面を離れる際に閉じたはずの接続が裏で復活し続け、通信量と接続数だけが増える。
切断そのものを減らすには、無通信の時間を作らないことが効く。RFC 6455 は制御フレームとして Ping (オペコード 0x9) と Pong (0xA) を定めており、Ping を受け取った側は Pong を返さなければならない。ただしブラウザーの WebSocket API は Ping を送る手段を公開していないため、クライアント側から死活を確かめたい場合はアプリケーション独自の軽いメッセージを定期的に往復させる。サーバー側のライブラリは Ping と Pong を扱えるものが多いので、サーバーから打つ方が素直である。
現場での応用を知るには関連書籍も役立つ。
この記事は役に立ちましたか?
関連用語
WebSocket API
API Gateway の WebSocket API でサーバーレスなリアルタイム双方向通信を実現する仕組み
ロングポーリング
サーバーがデータの準備ができるまでレスポンスを保留し、擬似的なリアルタイム通信を実現する手法
SSE (Server-Sent Events) とは - サーバーからのリアルタイム配信の仕組み
SSE はサーバーからクライアントへ HTTP 上で一方向のリアルタイムストリームを送る仕組み。WebSocket との違い / EventSource API の使い方 / 再接続処理を解説
TCP 3-way ハンドシェイク
TCP 接続を確立するための 3 段階の手順で、SYN → SYN-ACK → ACK の順で行われる
TCP/IP
インターネット通信の基盤となるプロトコル群で、データの分割 / 送信 / 再組立を信頼性高く行う
HTTP/3
QUIC の上で動作する HTTP のメジャーバージョン
関連する記事
インフラ / クラウド本ガイド - AWS や Docker を本で学ぶ
クラウドインフラ、コンテナ、IaC を学べる技術書の選び方と学習順序を紹介。インフラ本の賞味期限問題と公式ドキュメントとの使い分けも解説します。
技術書の中古本 / 古本の賢い買い方 - 安く手に入れて学ぶ
技術書を中古で買うときの判断基準と注意点を解説します。中古で買っていい本と新品で買うべき本の見分け方、状態チェックのポイントも紹介します。
AWS 本の選び方 - 全体像 / 構築 / 設計 / 運用 / セキュリティの 5 視点
AWS を学ぶ技術書の選び方を「全体像 / 構築 / 設計 / 運用 / セキュリティ」の 5 視点で整理。公式ドキュメントと本の役割分担、資格対策書の位置づけまで、2026 年 8 月時点の定番書で AWS 独学のルートを解説します。