---
title: WebSocket と SSE
url: https://doc.liz6.com/ja/networking/06-HTTP/06-websocket-and-sse
locale: ja
area: networking
tags:
- networking
- HTTP
date: 2026-06-30
modified: 2026-07-16
description: HTTP のリクエスト・レスポンスモデルはリアルタイムシーンには不向き——WebSocket はフルデュプレックスチャネルにアップグレードされ、SSE は単方向ストリームで簡潔さを保ちます。どちらを選ぶかは「サーバーがプッシュを必要とするか」「バイナリフレームを必要とするか」にかかっています。
---

# WebSocket と SSE

> HTTP のリクエスト・レスポンスモデルはリアルタイムシーンには不向き——WebSocket はフルデュプレックスチャネルにアップグレードされ、SSE は単方向ストリームで簡潔さを保ちます。どちらを選ぶかは「サーバーがプッシュを必要とするか」「バイナリフレームを必要とするか」にかかっています。

## 概要

WebSocket（RFC 6455, 2011）は HTTP ハンドシェイクに基づいて永続的な双方向 TCP チャネルを作成します——ブラウザとサーバーはいつでも互いにメッセージを送信できます。これは HTTP の「リクエスト・レスポンス」という単方向の制限を解決し、リアルタイムアプリケーション（チャット、ゲーム、ライブ配信の弾幕コメント、Grafana Live など）を可能にします。SSE（Server-Sent Events）はよりシンプルな単方向の代替手段です：サーバーはイベントストリームをクライアントにプッシュし、純粋な HTTP に基づいており、ファイアウォールとの互換性がより優れています。両方ともハートビートメカニズム（ping/pong vs SSE の自動再接続）を持っており、NAT/proxy 環境でロングコネクションが切れずに保たれます。

## WebSocket アップグレード

```
Client → Server: GET /ws HTTP/1.1
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==   ← ランダム、base64
  Sec-WebSocket-Version: 13
  Sec-WebSocket-Protocol: chat                    ← オプションのサブプロトコル

Server → Client: HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  ← SHA1(Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
  Sec-WebSocket-Protocol: chat

  → ハンドシェイク完了 → TCP 接続が双方向 WebSocket にアップグレード
```

## WebSocket フレーム

```
 0                   1                   2                   3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |        (16/64)              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Masking-key (if MASK set, 4 bytes)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Payload Data (可変、クライアントからの場合はマスク済み)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

FIN(1): 1 = これはメッセージの最後のフレーム
opcode(4): 0=continuation, 1=text(UTF-8), 2=binary, 8=close, 9=ping, 10=pong
MASK(1): クライアント→サーバーはマスク必須、サーバー→クライアントはマスク不可

Masking (クライアント → サーバー):
  1. クライアント: 4バイトのマスクキーをランダム生成
  2. ペイロードの各バイトに対して: payload[i] = original[i] XOR key[i % 4]
  3. 目的: 中間プロキシによるキャッシュポイズニングを防ぐ（攻撃者がブラウザに作成された WS ペイロードを送信させ、
     → プロキシが誤って HTTP レスポンスとして解析し、キャッシュを破損させるのを防ぐ）

Close フレーム (opcode=8):
  [ステータスコード (2バイト、ビッグエンディアン)][理由 (UTF-8、オプション)]
  1000: 正常なクローズ
  1001: 終了（サーバーシャットダウン、ページ遷移）
```

## Ping/Pong (ハートビート)

```
Ping フレーム (opcode=9): オプションのペイロード (アプリケーション固有)
Pong フレーム (opcode=10): ping のペイロードをコピー必須

→ アプリケーション層のハートビート (TCP keepalive とは異なる)
→ 検出: 死んだ接続 (TCP は生きていてもリモートアプリがハングしている場合)

プロキシ/サーバーのタイムアウト: nginx の proxy_read_timeout は WebSocket に適用される →
  ping/pong がなければ、接続はプロキシのタイムアウトで切断される可能性がある
```

## SSE (Server-Sent Events)

単方向 (サーバー → クライアント) ストリーム、HTTP chunked encoding に基づく：

```
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

id: 42                         ← 最後のイベント ID (この ID から自動再接続)
event: alert                   ← オプションのイベントタイプ (指定しない場合は "message")
data: {"status":"firing"}      ← イベントデータ

data: 複数行のデータ
data: 2行目
                               ← 空行 → イベントディスパッチ

クライアント API: EventSource (HTML5 組み込み):
  const es = new EventSource("/events");
  es.onmessage = (e) => { console.log(e.data); };
  es.addEventListener("alert", (e) => { ... });
```

## WebSocket vs SSE

| | WebSocket | SSE |
|---|---|---|
| 方向 | 双方向 | サーバー → クライアント |
| プロトコル | WebSocket (アップグレード) | HTTP chunked |
| バイナリデータ | 標準 (opcode=2) | base64 エンコードが必要 |
| ファイアウォール互換性 | 低い (一部のプロキシはアップグレードをサポートしない) | 高い (純粋な HTTP) |
| 自動再接続 | 手動 | EventSource が自動 (last-event-id から) |
| 適した用途 | チャット、ゲーム、リアルタイム共同作業 | 通知、フィード、リアルタイムデータプッシュ |

## 参考

- **RFC**: 6455
- **SSE**: html.spec.whatwg.org/multipage/server-sent-events.html
- **WS テスト**: websocat (CLI WebSocket クライアント)

*キーワード: WebSocket, アップグレード, フレーム, マスキング, ping/pong, SSE, EventSource, ハートビート*
