WEB PROTOCOL

WebSocket(双方向リアルタイム通信)

HTTPハンドシェイクから始まる永続的な全二重通信プロトコル。Slackやオンラインゲームに使われています。

INTERACTIVE VISUALIZATION
HTTP Upgrade
101 Switching Protocols
WebSocket フレーム
Close フレーム
接続状態
未接続
サブプロトコル
chat
クローズコード
1000 Normal Closure
通信方式
全二重(双方向)
シナリオを選択
Slack風のリアルタイムチャットアプリ。メッセージを送信すると全員に即時配信されます
ステップ: 1 / 7初期状態
クライアントとサーバーがTCP接続前の待機状態です。WebSocketは最初に通常のHTTPリクエストを使ってプロトコルを切り替えます。
UPGRADE REQUEST
GET /ws/chat HTTP/1.1
Host:chat.example.com
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version:13
UPGRADE RESPONSE
サーバーの応答を待機中...
解説

1
WebSocketとは

WebSocket は、1回の HTTP ハンドシェイクで接続を確立した後、その接続を使い続けて双方向にメッセージを交換できるプロトコルです(RFC 6455)。

通常の HTTP では、クライアントがリクエストを送り、サーバーがレスポンスを返す、という1往復ごとに完結する通信です。サーバーからクライアントに「新しいデータがあるよ」と自発的に通知することはできません。

WebSocket は接続を繋ぎっぱなしにして、いつでも両側からデータを送れる仕組みです。チャットアプリで相手のメッセージがリアルタイムに表示されたり、株価がリアルタイムに更新されたりするのは WebSocket のおかげです。

なぜ HTTP ではダメなのか

HTTP でリアルタイム通信をしようとすると、クライアントが「何か新しいデータありますか?」と定期的にサーバーに問い合わせるポーリングが必要です。しかしこれには問題があります。

データがなくても毎回リクエストを送るため通信量が無駄
ポーリング間隔の分だけ遅延が発生する(1秒間隔なら最大1秒遅れ)
毎回 HTTP ヘッダー(数百バイト)が付くためサーバー負荷が高い

WebSocket はこれらの問題をすべて解決します。接続を1回確立すれば、データが発生した瞬間にサーバーからプッシュでき、ヘッダーオーバーヘッドも最小(2バイト〜)です。

接続の流れ(4ステップ)
HTTP アップグレード — クライアントが Upgrade: websocket ヘッダーで「WebSocket に切り替えたい」とリクエスト
接続確立 — サーバーが 101 Switching Protocols で承認。以降は HTTP ではなく WebSocket プロトコルで通信
メッセージ交換 — クライアント↔サーバーが自由にメッセージを送り合う。テキスト(JSON等)やバイナリ(画像等)に対応
クローズ — どちらかが Close フレームを送って接続を終了
// クライアント側(JavaScript)
const ws = new WebSocket("wss://chat.example.com")
// 接続が確立されたとき
ws.onopen = () => ws.send("Hello!")
// サーバーからメッセージが届いたとき
ws.onmessage = (event) => console.log(event.data)
// たった4行でリアルタイム双方向通信が実現

2
WebSocketの特徴

  • 全二重通信:クライアントとサーバーが同時に独立してメッセージを送受信できます。HTTPでは「クライアントがリクエストを送り、サーバーが返す」という一方通行の繰り返しですが、WebSocketは電話のように両者がいつでも話せます。たとえばSlackでは自分がメッセージを入力している間も、他のメンバーの発言がリアルタイムに届きます。
  • 📦低オーバーヘッド:WebSocketフレームのヘッダーは最小2バイトです。一方、HTTPリクエストには HostCookieUser-Agent などのヘッダーが毎回数百バイト付きます。株価アプリのように1秒間に何十回もデータを更新する場合、この差は通信量と遅延に大きく影響します。
  • 📡サーバープッシュ:クライアントからのリクエストを待たず、サーバー側の都合でいつでもデータを送信できます。HTTPでは「何か届いていますか?」と定期的に問い合わせる(ポーリング)しかありませんでした。WebSocketならGitHubのPRマージ通知やFigmaでの他ユーザーの編集操作が、発生した瞬間にブラウザへ届きます。
  • 🔗持続接続:最初のHTTPハンドシェイクで確立した1本のTCPコネクションを使い続けます。HTTPはリクエストのたびにTCP接続の確立(3-wayハンドシェイク)とTLS接続のコストが発生しますが、WebSocketはその手続きが最初の1回だけです。オンラインゲームのように数ミリ秒単位で状態を同期する用途では、この差が体感できるほどのレイテンシ改善につながります。

3
ユースケース

チャット
Slack、Discord、LINEなど。送信したメッセージが全参加者に即時配信される
リアルタイム市況
Bloomberg、Binanceなど。サーバーから一方的に株価・暗号通貨の価格をプッシュ配信
オンラインゲーム
ブラウザゲームやマルチプレイヤーゲームの状態同期。低レイテンシの双方向通信が必須
通知・コラボレーション
GitHub、Figma、Notionなど。PRのマージ通知や他ユーザーの編集内容をリアルタイム反映

4
用語解説

ハンドシェイク
= WebSocket接続の開始手続き
クライアントが Upgrade: websocketConnection: Upgrade を含むHTTP GETリクエストを送ります。サーバーが 101 Switching Protocols で応答した瞬間、HTTPの通信は終わりWebSocketプロトコルへ切り替わります。この切り替えは1回だけ行われ、以後は同じTCP接続を使い続けます。
CSGET Upgrade101 Switching
フレーム
= WebSocketでやり取りするデータの最小単位
接続確立後、すべてのデータは「フレーム」という単位で送受信されます。フレームは ヘッダー(最小2バイト)+ペイロード で構成されます。ヘッダーにはopcode(種別)・ペイロード長・マスクビットが含まれます。HTTPのように毎回 Content-TypeCookie などの数百バイトのヘッダーを付け直す必要がなく、オーバーヘッドが極めて小さいのが特徴です。上のツールのステップ4・5で「Text Frame」として表示されているのがこのフレームです。
opcode0x1payload"hello"
opcode
= フレームの種別を表す4ビット値
各フレームのヘッダーに含まれる4ビットの識別子で、受信側はこの値を見てデータの扱い方を決めます。0x1(テキスト)はUTF-8文字列、0x2(バイナリ)は任意のバイナリデータ、0x8(クローズ)は接続終了の合図、0x9(Ping)/ 0xA(Pong)は死活確認です。上のツールのDetail panelには opcode: 0x1opcode: 0x8 として表示されます。
0x1text0x8close0x9ping
Ping / Pong
= 接続の死活監視フレーム
WebSocketの接続は一度確立すると長時間維持されますが、途中でネットワークが切れていても気づかないことがあります。そこでどちらかが Ping(opcode: 0x9) を送ると、受信側は必ず Pong(opcode: 0xA) で応答します。一定時間内にPongが返ってこなければ接続が切れたと判断し、再接続処理を行います。上のツールの「プッシュ通知」シナリオのステップ4で「Ping Frame」として確認できます。
PingPong

5
WebSocketメッセージの構造

WebSocket では HTTP のリクエスト/レスポンスの代わりにフレームという単位でデータをやり取りします。フレームのヘッダーは最小2バイトで、HTTP ヘッダー(数百バイト)と比べて非常に軽量です。

HTTP では毎回 Host, Cookie, User-Agent などのヘッダーが付きますが、WebSocket フレームにはそれらが一切ありません。1秒間に何十回もデータを送る用途(株価、ゲーム等)では、この差が通信量とレイテンシに大きく影響します。

FIN + opcode1 バイトMASK + length1 バイトMasking key4B(client→のみ)Payload data可変長ヘッダー: 最小 2 バイト(HTTP の約 1/100)HTTP: Host + Cookie + User-Agent + ... = 数百バイトWebSocket: 2 バイト + ペイロード
FIN + opcode(1バイト目)

FIN(1ビット): このフレームがメッセージの最後かどうか。大きなデータは複数フレームに分割(フラグメンテーション)されます。

opcode(4ビット): フレームの種類を示します。

0x1 = テキスト(UTF-8)0x2 = バイナリ0x8 = Close(切断)0x9 = Ping(死活確認)0xA = Pong(応答)
MASK + payload length(2バイト目)

MASK(1ビット): クライアント → サーバーの通信では必ず 1(マスクあり)。サーバー → クライアントは 0(マスクなし)。これはプロキシの誤動作を防ぐためのルールです(RFC 6455)。payload length(7ビット): ペイロードのバイト数。125以下なら7ビットに直接格納、126以上なら後続の2バイトまたは8バイトで拡張します。

Masking key(4バイト、クライアント→のみ)

クライアントからサーバーに送るフレームにだけ付くランダムな4バイトです。ペイロードの各バイトと XOR して「マスク」します。暗号化ではなく、キャッシュポイズニング攻撃を防ぐための仕組みです。サーバー側で同じキーで XOR すれば元に戻ります。

具体例: "Hello" を送るフレーム
// クライアント → サーバー: "Hello" (5バイト)
0x81 ← FIN=1(最後のフレーム), opcode=0x1(テキスト)
0x85 ← MASK=1(クライアント送信), length=5
0x37 0xfa 0x21 0x3d ← masking key(ランダム)
0x7f 0x9f 0x4d 0x51 0x58 ← "Hello" をマスクした結果
// 合計: 2 + 4 + 5 = 11 バイト
// HTTP なら同じ "Hello" を送るのに 200バイト以上のヘッダーが付く

6
WebSocket通信の手順

1
HTTP GET + Upgrade
クライアントが Upgrade: websocket ヘッダーを含むHTTP GETリクエストを送信します。
ClientServerGET Upgrade
2
101 Switching Protocols
サーバーが Sec-WebSocket-Accept キーを含む101レスポンスを返し、プロトコル切り替えを承認します。
ClientServer101 Switching
3
WebSocket接続確立
TCPコネクションが引き継がれ、双方向フレーム通信が開始します。HTTPとしての通信は終了し、全二重通信が可能になります。
ClientServerWS
4
メッセージ交換
クライアント・サーバー双方から任意のタイミングでフレームを送信できます。テキスト/バイナリ/制御フレームを使い分けます。
ClientServer
5
クローズハンドシェイク
どちらかがCloseフレームを送信し、相手がCloseで応答した後にTCPコネクションを切断します。クローズコード(1000=正常終了など)で理由を伝えます。
ClientServerClose frame

7
HTTP vs WebSocket

比較項目HTTPWebSocket
通信方向単方向(リクエスト→レスポンス)双方向(全二重)
接続の持続リクエストごとに接続/切断1回接続して使い続ける
サーバープッシュ不可(SSEは一方向のみ)可能(いつでも送信)
ヘッダーサイズ数百バイト〜数KB2〜14バイト(最小)
リアルタイム性ポーリングが必要即時(低レイテンシ)
用途REST API・Webページチャット・ゲーム・通知

8
セキュリティ

やってはいけないこと
  • 本番環境でws://(暗号化なし)を使う
  • Originヘッダーを検証しない(CSWSHリスク)
  • 認証なしでWebSocket接続を許可する
  • 受信メッセージをサニタイズせずに処理する
ベストプラクティス
  • 本番環境では必ずwss://(TLS暗号化)を使う
  • Originヘッダーを検証してCSWSHを防ぐ
  • JWTをコネクション確立時のURLまたは最初のメッセージで渡す
  • 受信メッセージのサイズ制限とレート制限を設ける

関連コンテンツ