T-CREATOR

WebSocket の仕組みを図解で理解する

WebSocket の仕組みを図解で理解する

WebSocket は、現代の Web アプリケーションにおいて欠かせないリアルタイム通信技術です。従来の HTTP 通信では実現が困難だった双方向通信を可能にし、チャットアプリケーション、オンラインゲーム、リアルタイム株価表示など、様々な場面で活用されています。

しかし、WebSocket の仕組みを理解するには、従来の HTTP 通信の制約から始まり、なぜ WebSocket が必要になったのか、そしてどのような技術的な解決策を提供しているのかを段階的に理解することが重要です。本記事では、図解を交えながら WebSocket の仕組みを初心者の方にも分かりやすく解説いたします。

背景

HTTP 通信の制約とリクエスト・レスポンス型通信の限界

Web の基盤技術である HTTP プロトコルは、クライアント(ブラウザ)からサーバーへリクエストを送信し、サーバーがレスポンスを返すという「リクエスト・レスポンス型」の通信方式を採用しています。この仕組みは静的な Web ページの表示には適していますが、リアルタイムな双方向通信には大きな制約がありました。

HTTP の基本的な通信フローを図で確認してみましょう。

mermaidsequenceDiagram
    participant Client as クライアント
    participant Server as サーバー

    Client->>Server: HTTPリクエスト送信
    Note over Server: リクエスト処理
    Server->>Client: HTTPレスポンス送信
    Note over Client: レスポンス受信・完了

    Note over Client, Server: 通信終了(接続切断)

    Client->>Server: 新しいリクエスト(再接続)
    Server->>Client: 新しいレスポンス

補足:HTTP 通信では、各リクエスト・レスポンスのペアごとに接続が切断され、継続的な通信ができません。

リアルタイム通信の必要性の高まり

インターネットの普及と共に、Web アプリケーションはより高度で双方向的な機能が求められるようになりました。特に以下のような用途では、リアルタイムでの情報交換が必要不可欠です。

分野具体例要求される機能
コミュニケーションチャットアプリ、ビデオ会議瞬時のメッセージ交換
金融株価・FX 取引システムリアルタイム価格更新
ゲームオンラインマルチプレイプレイヤー間の同期
コラボレーション共同編集ツール複数ユーザーの同時操作

WebSocket 登場の経緯

これらの需要に応えるため、HTML5 の仕様策定と同時期に、WebSocket プロトコルが開発されました。WebSocket は 2011 年に RFC 6455 として標準化され、現在では主要なブラウザでサポートされています。

WebSocket 登場までの技術的な変遷を時系列で見てみましょう。

mermaidtimeline
    title リアルタイム通信技術の進化

    1990年代 : HTTP/1.0
             : 基本的なWebページ表示

    2000年代前半 : HTTP/1.1 + Ajax
                 : 非同期通信の実現

    2000年代後半 : Long Polling
                 : 疑似リアルタイム通信

    2010年代 : WebSocket (RFC 6455)
             : 真のリアルタイム双方向通信

    現在 : WebSocket + WebRTC
         : 高度なリアルタイムアプリ

図で理解できる要点:

  • HTTP 通信の制約により、リアルタイム通信は長らく困難でした
  • Ajax、Long Polling などの回避策は存在しましたが、根本的な解決には至りませんでした
  • WebSocket の登場により、真の双方向リアルタイム通信が実現されました

課題

HTTP のポーリングによる非効率性

従来の HTTP 通信でリアルタイム性を実現する手法として「ポーリング」がありました。しかし、この手法には深刻な非効率性の問題がありました。

ポーリングの仕組みと問題点を図で確認してみましょう。

mermaidsequenceDiagram
    participant Client as クライアント
    participant Server as サーバー

    Note over Client: 定期的にリクエスト送信

    loop 毎秒ポーリング
        Client->>Server: データ更新確認
        alt データ更新あり
            Server->>Client: 新しいデータ
        else データ更新なし
            Server->>Client: 変更なし(空レスポンス)
        end
        Note over Client: 1秒待機
    end

    Note over Client, Server: 無駄なリクエストが多発

ポーリングの主な問題点は以下の通りです:

  • 無駄なリクエスト: 更新がない場合でも定期的にリクエストを送信
  • レスポンス遅延: ポーリング間隔によって最大遅延が決まる
  • サーバー負荷: 大量の無意味なリクエストによる負荷増大
  • 帯域幅の浪費: HTTP ヘッダーによるオーバーヘッド

サーバープッシュ通信の困難さ

HTTP の根本的な制約として、サーバーから能動的にクライアントへ情報を送信する「サーバープッシュ」が困難でした。これは、HTTP がクライアント主導の通信プロトコルとして設計されているためです。

mermaidflowchart TD
    subgraph HTTP通信の制約
        A[サーバー] -->|❌ 直接送信不可| B[クライアント]
        B -->|✅ リクエスト可能| A
    end

    subgraph 回避策の問題
        C[Long Polling] -->|複雑な実装| D[疑似リアルタイム]
        E[Server-Sent Events] -->|単方向のみ| F[制限的なプッシュ]
    end

    subgraph WebSocketによる解決
        G[サーバー] <-->|✅ 双方向通信| H[クライアント]
    end

レイテンシとリソース消費の問題

従来の手法では、以下のような深刻な問題が発生していました:

問題詳細影響
高レイテンシポーリング間隔による遅延ユーザー体験の悪化
帯域幅浪費不要な HTTP ヘッダー送信ネットワーク負荷増大
サーバー負荷大量の接続・切断処理スケーラビリティの問題
バッテリー消耗頻繁な通信による電力消費モバイル端末への負荷

補足:これらの課題は、特に同時接続数が多いアプリケーションにおいて顕著に現れ、サービスの安定性やコスト面で大きな問題となっていました。

解決策

WebSocket プロトコルの仕組み

WebSocket は、これまでに挙げた課題を根本的に解決する新しいプロトコルです。HTTP と異なり、一度接続を確立すると、その接続を維持したまま双方向でリアルタイム通信を行うことができます。

WebSocket の基本的な通信フローを見てみましょう。

mermaidsequenceDiagram
    participant Client as クライアント
    participant Server as サーバー

    Note over Client, Server: 1. ハンドシェイク段階
    Client->>Server: HTTP Upgradeリクエスト
    Server->>Client: WebSocket接続承認

    Note over Client, Server: 2. WebSocket通信段階
    rect rgb(230, 255, 230)
        Note over Client, Server: 接続維持・双方向通信
        Client->>Server: メッセージ送信
        Server->>Client: メッセージ送信
        Client->>Server: メッセージ送信
        Server->>Client: メッセージ送信
    end

    Note over Client, Server: 3. 接続終了
    Client->>Server: 接続終了要求
    Server->>Client: 接続終了確認

補足:WebSocket では、最初のハンドシェイク後は軽量なフレーム形式でデータを交換するため、HTTP ヘッダーのオーバーヘッドが大幅に削減されます。

ハンドシェイク処理の詳細

WebSocket の接続確立は、HTTP の「Upgrade」メカニズムを利用して行われます。この巧妙な仕組みにより、既存の Web インフラストラクチャーとの互換性を保ちながら、新しいプロトコルへの移行が可能になりました。

ハンドシェイクの詳細な流れは以下の通りです:

クライアントからの Upgrade リクエスト

httpGET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

サーバーからの承認レスポンス

httpHTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

この時点で、HTTP プロトコルから WebSocket プロトコルへの切り替えが完了し、以降は専用のフレーム形式での通信が開始されます。

双方向通信の実現方法

WebSocket 接続が確立された後、クライアントとサーバーは以下のような特徴を持つ通信が可能になります:

mermaidflowchart LR
    subgraph WebSocket接続
        A[クライアント] <-->|フレーム送信| B[サーバー]
    end

    subgraph 通信の特徴
        C[低レイテンシ]
        D[低オーバーヘッド]
        E[双方向]
        F[リアルタイム]
    end

    subgraph データ形式
        G[テキストフレーム]
        H[バイナリフレーム]
        I[制御フレーム]
    end

    A -.-> C
    A -.-> D
    A -.-> E
    A -.-> F

    B -.-> G
    B -.-> H
    B -.-> I

WebSocket フレームの構造も非常に効率的に設計されています:

フィールドサイズ説明
FIN1bitフラグメント終了フラグ
Opcode4bitフレームタイプ(テキスト/バイナリ/制御)
Payload Length7+16/64bitペイロードサイズ
Masking Key32bitクライアント送信時のマスクキー
Payload Data可変実際のデータ

図で理解できる要点:

  • WebSocket は最初に HTTP を使ってプロトコル変更を行います
  • 接続確立後は専用の軽量フレーム形式で効率的な通信を行います
  • 双方向通信により、サーバーからもクライアントへ能動的に情報を送信できます

具体例

WebSocket を使ったチャットアプリケーション

最も分かりやすい WebSocket の活用例として、リアルタイムチャットアプリケーションを実装してみましょう。従来の HTTP 通信との違いを明確に理解できます。

まず、チャットアプリケーションの全体的なアーキテクチャを図で確認します。

mermaidflowchart TB
    subgraph Client側
        A[ブラウザA]
        B[ブラウザB]
        C[ブラウザC]
    end

    subgraph Server側
        D[WebSocketサーバー]
        E[メッセージ配信エンジン]
        F[(接続管理)]
    end

    A <-->|WebSocket接続| D
    B <-->|WebSocket接続| D
    C <-->|WebSocket接続| D

    D --> E
    E --> F

    Note1[ユーザーAがメッセージ送信]
    Note2[全接続ユーザーに即座に配信]

    A -.-> Note1
    D -.-> Note2

サーバーサイドの実装

Node.js と ws(WebSocket ライブラリ)を使用したサーバー実装の基本構造です:

javascript// WebSocketサーバーの初期化
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// 接続中のクライアント管理
const clients = new Set();

接続管理とメッセージ配信の処理:

javascript// 新しいクライアント接続時の処理
wss.on('connection', (ws) => {
  // クライアントをセットに追加
  clients.add(ws);
  console.log('新しいクライアントが接続しました');

  // 接続確認メッセージ送信
  ws.send(
    JSON.stringify({
      type: 'system',
      message: 'チャットサーバーに接続しました',
    })
  );
});

リアルタイムメッセージ処理:

javascript// メッセージ受信時の処理
ws.on('message', (data) => {
  try {
    const message = JSON.parse(data);

    // 受信したメッセージを全クライアントに配信
    const broadcastMessage = {
      type: 'message',
      username: message.username,
      text: message.text,
      timestamp: new Date().toISOString(),
    };

    // 全ての接続中クライアントに送信
    clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(broadcastMessage));
      }
    });
  } catch (error) {
    console.error('メッセージ処理エラー:', error);
  }
});

接続終了処理:

javascript// クライアント切断時の処理
ws.on('close', () => {
  clients.delete(ws);
  console.log('クライアントが切断しました');
});

クライアントサイドの実装

ブラウザ側の WebSocket 接続とメッセージ処理:

javascript// WebSocket接続の確立
const socket = new WebSocket('ws://localhost:8080');

// DOM要素の取得
const messageInput =
  document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const messagesContainer =
  document.getElementById('messages');

接続イベントの処理:

javascript// 接続確立時の処理
socket.addEventListener('open', (event) => {
  console.log('サーバーに接続しました');
  updateConnectionStatus('接続中');
});

// エラー発生時の処理
socket.addEventListener('error', (error) => {
  console.error('WebSocketエラー:', error);
  updateConnectionStatus('エラー');
});

メッセージ送受信の処理:

javascript// メッセージ受信時の処理
socket.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'message') {
    displayMessage(data);
  } else if (data.type === 'system') {
    displaySystemMessage(data.message);
  }
});

// メッセージ送信処理
function sendMessage() {
  const messageText = messageInput.value.trim();
  if (messageText && socket.readyState === WebSocket.OPEN) {
    const message = {
      username: getCurrentUsername(),
      text: messageText,
    };

    socket.send(JSON.stringify(message));
    messageInput.value = ''; // 入力欄をクリア
  }
}

リアルタイム株価表示システム

金融システムでは、株価データのリアルタイム配信が重要です。WebSocket を活用した株価表示システムの実装例を見てみましょう。

株価データ配信システムのアーキテクチャ:

mermaidflowchart LR
    subgraph データソース
        A[証券取引所API]
        B[市場データフィード]
    end

    subgraph サーバー側
        C[データ収集サービス]
        D[WebSocketサーバー]
        E[株価データ処理]
        F[(価格データベース)]
    end

    subgraph クライアント側
        G[取引画面]
        H[チャート表示]
        I[アラート機能]
    end

    A --> C
    B --> C
    C --> E
    E --> F
    E --> D
    D <--> G
    D <--> H
    D <--> I

株価データ配信サーバー

javascript// 株価データ管理
class StockPriceServer {
  constructor() {
    this.wss = new WebSocket.Server({ port: 8081 });
    this.subscribers = new Map(); // 銘柄コード -> クライアント配列
    this.currentPrices = new Map();

    this.setupWebSocketServer();
    this.startPriceUpdates();
  }
}

クライアントの購読管理:

javascript  setupWebSocketServer() {
    this.wss.on('connection', (ws) => {
      ws.on('message', (data) => {
        const request = JSON.parse(data);

        if (request.action === 'subscribe') {
          this.subscribeToStock(ws, request.symbol);
        } else if (request.action === 'unsubscribe') {
          this.unsubscribeFromStock(ws, request.symbol);
        }
      });

      ws.on('close', () => {
        this.removeClientFromAllSubscriptions(ws);
      });
    });
  }

リアルタイム価格更新処理:

javascript  // 模擬価格データ生成(実際は市場データを使用)
  startPriceUpdates() {
    setInterval(() => {
      const symbols = ['AAPL', 'GOOGL', 'MSFT', 'TSLA'];

      symbols.forEach(symbol => {
        const currentPrice = this.currentPrices.get(symbol) || 100;
        const change = (Math.random() - 0.5) * 2; // -1 to +1
        const newPrice = Math.max(currentPrice + change, 0.01);

        this.updateStockPrice(symbol, newPrice);
      });
    }, 1000);
  }

  updateStockPrice(symbol, price) {
    this.currentPrices.set(symbol, price);

    // 該当銘柄を購読しているクライアントに配信
    const subscribers = this.subscribers.get(symbol);
    if (subscribers) {
      const priceUpdate = {
        symbol: symbol,
        price: price.toFixed(2),
        timestamp: Date.now(),
        change: this.calculatePriceChange(symbol, price)
      };

      subscribers.forEach(client => {
        if (client.readyState === WebSocket.OPEN) {
          client.send(JSON.stringify(priceUpdate));
        }
      });
    }
  }

オンラインゲームでの活用

オンラインマルチプレイゲームでは、プレイヤー間の行動同期とゲーム状態の共有が必要です。WebSocket を使った簡単なリアルタイムゲームの例を見てみましょう。

ゲーム状態同期の仕組み:

mermaidstateDiagram-v2
    [*] --> Waiting: プレイヤー待機
    Waiting --> GameStart: 2人以上参加
    GameStart --> Playing: ゲーム開始
    Playing --> Playing: プレイヤーアクション
    Playing --> GameEnd: ゲーム終了条件
    GameEnd --> Waiting: 次ゲーム待機

    note right of Playing: 全プレイヤーに<br/>リアルタイム同期

ゲームサーバーの実装

javascript// ゲームルーム管理
class GameServer {
  constructor() {
    this.rooms = new Map();
    this.wss = new WebSocket.Server({ port: 8082 });
    this.setupServer();
  }

  setupServer() {
    this.wss.on('connection', (ws) => {
      ws.playerId = this.generatePlayerId();

      ws.on('message', (data) => {
        const action = JSON.parse(data);
        this.handlePlayerAction(ws, action);
      });
    });
  }
}

プレイヤーアクションの処理と同期:

javascript  handlePlayerAction(ws, action) {
    const room = this.findPlayerRoom(ws.playerId);
    if (!room) return;

    switch (action.type) {
      case 'move':
        this.handlePlayerMove(room, ws.playerId, action.data);
        break;
      case 'attack':
        this.handlePlayerAttack(room, ws.playerId, action.data);
        break;
    }

    // ゲーム状態を全プレイヤーに配信
    this.broadcastGameState(room);
  }

  broadcastGameState(room) {
    const gameState = {
      type: 'gameState',
      players: room.players,
      timestamp: Date.now()
    };

    room.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(gameState));
      }
    });
  }

図で理解できる要点:

  • チャットアプリでは、一人のメッセージが瞬時に全員に配信されます
  • 株価システムでは、価格変動を購読者にリアルタイムで通知します
  • ゲームでは、プレイヤーアクションを即座に他プレイヤーに同期します

まとめ

WebSocket は、従来の HTTP 通信の制約を根本的に解決し、真のリアルタイム双方向通信を実現する画期的な技術です。本記事で解説した内容をまとめると、以下の点が重要なポイントとなります。

まず、背景として理解すべき点は、HTTP 通信のリクエスト・レスポンス型という基本的な制約です。この仕組みは静的な Web ページには適していましたが、リアルタイム通信には根本的な限界がありました。

次に、従来の課題として、ポーリングによる非効率性、サーバープッシュの困難さ、そして高いレイテンシとリソース消費の問題がありました。これらは特に、同時接続数が多いアプリケーションにおいて深刻な影響を与えていました。

WebSocket による解決策では、HTTP の Upgrade メカニズムを巧妙に活用し、既存インフラとの互換性を保ちながら新しいプロトコルへの移行を実現しています。ハンドシェイク完了後は、軽量なフレーム形式での効率的な双方向通信が可能になります。

具体的な活用例として、チャットアプリケーション、株価表示システム、オンラインゲームでの実装を通じて、WebSocket の実用性と効果を確認できました。これらの例では、従来の HTTP 通信では実現困難だった、瞬時の情報共有とリアルタイム同期が実現されています。

WebSocket の採用により、以下のような具体的なメリットが得られます:

  • 低レイテンシ: ポーリング不要によるリアルタイム通信
  • 低オーバーヘッド: 軽量フレーム形式による効率化
  • サーバー負荷軽減: 無駄な HTTP リクエストの削減
  • 優れたユーザー体験: 瞬時のデータ更新とレスポンシブな操作感

現在では、WebSocket は多くのブラウザでネイティブサポートされ、Node.js、Python、Java、C#など様々な言語で豊富なライブラリが提供されています。モダンな Web アプリケーション開発において、WebSocket は欠かせない技術の一つとなっているのです。

リアルタイム通信が必要なアプリケーションを開発する際は、ぜひ WebSocket の活用を検討してみてください。適切に実装することで、ユーザーにとって魅力的で応答性の高い Web アプリケーションを構築することができるでしょう。

関連リンク