T-CREATOR

WebSocket と HTTP/2・HTTP/3 の違いを徹底比較

WebSocket と HTTP/2・HTTP/3 の違いを徹底比較

Web アプリケーションの性能と体験を左右する重要な要素の一つが、通信プロトコルの選択です。リアルタイム性が求められるチャットアプリや動的な株価表示、大量のデータを効率的に転送する API サーバーなど、用途によって最適なプロトコルは大きく異なります。

今回は、モダン Web 開発において特に重要な 3 つのプロトコル「WebSocket」「HTTP/2」「HTTP/3」について、それぞれの特徴や適用場面を詳しく比較し、あなたの開発プロジェクトに最適な選択肢を見つけるお手伝いをします。

背景

WebSocket の登場とリアルタイム通信の需要

従来の HTTP/1.1 では、クライアントからのリクエストに対してサーバーがレスポンスを返すという一方向の通信モデルが基本でした。しかし、チャットアプリケーションやオンラインゲーム、株価のリアルタイム表示など、双方向でリアルタイムな通信が必要なアプリケーションの需要が急激に高まってきました。

このような背景から、2011 年に WebSocket プロトコルが標準化されました。WebSocket は、一度接続を確立すれば、サーバーとクライアント間で自由に双方向通信が可能になる画期的な技術です。

リアルタイム通信の需要が高まる理由を図で示します。

mermaidflowchart TD
    A[従来のWebアプリケーション] --> B[ポーリング方式]
    B --> C[頻繁なHTTPリクエスト]
    C --> D[サーバー負荷増大]
    D --> E[レスポンス遅延]

    F[リアルタイム通信の需要] --> G[チャットアプリ]
    F --> H[オンラインゲーム]
    F --> I[株価表示システム]

    G --> J[WebSocket導入]
    H --> J
    I --> J

    J --> K[常時接続維持]
    K --> L[即座のデータ送受信]

従来のポーリング方式では無駄な通信が発生していましたが、WebSocket により効率的なリアルタイム通信が実現されています。

HTTP/2・HTTP/3 の進化の背景

一方で、従来の Web ページ表示や RESTful API においても、HTTP/1.1 の限界が明らかになってきました。特に、モバイルデバイスの普及や高解像度画像の増加により、より効率的なデータ転送が求められるようになったのです。

HTTP/2 は 2015 年にリリースされ、多重化(マルチプレキシング)やヘッダー圧縮といった機能により、従来の HTTP の性能を大幅に向上させました。さらに、HTTP/3 は 2022 年に標準化され、UDP ベースの QUIC(Quick UDP Internet Connections)プロトコルを採用することで、より高速で信頼性の高い通信を実現しています。

HTTP 進化の流れを時系列で整理します。

mermaidtimeline
    title HTTPプロトコルの進化
    1991 : HTTP/1.0 : 基本的な要求・応答モデル
    1997 : HTTP/1.1 : 持続的接続 : パイプライニング
    2015 : HTTP/2 : バイナリプロトコル : 多重化 : ヘッダー圧縮
    2022 : HTTP/3 : QUIC採用 : UDP基盤 : 暗号化標準

各世代の HTTP が解決した課題と改善点が明確に分かります。

各プロトコルの基本特性

WebSocket の特徴

WebSocket は、HTTP/1.1 の制約を超えて双方向通信を実現するために設計されたプロトコルです。最初に HTTP でハンドシェイクを行い、その後 WebSocket プロトコルに切り替わる仕組みになっています。

主な特徴

特徴説明メリット
双方向通信クライアント・サーバー双方から任意のタイミングで通信可能リアルタイム性の実現
永続接続一度確立した接続を維持し続けるオーバーヘッドの削減
低レイテンシTCP コネクション上で直接データをやり取り即座の応答が可能
フレームベース軽量なフレーム形式でデータを送受信効率的なデータ転送

WebSocket の接続確立から通信までの流れは以下のようになります。

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

// 接続が開いた時の処理
socket.addEventListener('open', (event) => {
  console.log('WebSocket接続が確立されました');
});
javascript// メッセージの送信
function sendMessage(message) {
  socket.send(
    JSON.stringify({
      type: 'chat',
      content: message,
      timestamp: Date.now(),
    })
  );
}

// メッセージの受信
socket.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('受信:', data.content);
});

HTTP/2 の特徴

HTTP/2 は、HTTP/1.1 との互換性を保ちながら、パフォーマンスを大幅に改善したプロトコルです。テキストベースだった HTTP/1.1 に対して、バイナリ形式を採用することで効率化を図りました。

主な特徴

特徴説明メリット
多重化単一接続で複数のリクエストを並行処理ページ読み込み速度の向上
ヘッダー圧縮HPACK 圧縮によりヘッダーサイズを削減帯域幅の効率的利用
サーバープッシュサーバーが必要なリソースを先読み送信表示速度の改善
バイナリプロトコルテキストではなくバイナリ形式で通信処理速度の高速化

HTTP/2 の多重化機能により、複数のリクエストが効率的に処理される様子を図解します。

mermaidsequenceDiagram
    participant C as クライアント
    participant S as サーバー

    Note over C,S: HTTP/1.1の場合(直列処理)
    C->>S: リクエスト1 (CSS)
    S-->>C: レスポンス1
    C->>S: リクエスト2 (JS)
    S-->>C: レスポンス2
    C->>S: リクエスト3 (画像)
    S-->>C: レスポンス3

    Note over C,S: HTTP/2の場合(並列処理)
    C->>S: リクエスト1,2,3 (同時送信)
    S-->>C: レスポンス1,2,3 (並列処理)

HTTP/2 では複数のリクエストが同時に処理されるため、全体的な読み込み時間が大幅に短縮されます。

HTTP/3 の特徴

HTTP/3 は、TCP の代わりに UDP ベースの QUIC プロトコルを使用する、最新の HTTP バージョンです。Google の SPDY プロトコルの経験を活かし、より高速で安全な通信を実現しています。

主な特徴

特徴説明メリット
QUIC 採用UDP ベースの高速プロトコル使用接続確立の高速化
接続移行IP アドレス変更時も接続維持モバイル環境での安定性
標準暗号化TLS が標準で組み込まれているセキュリティの向上
ヘッドオブライン阻害解決パケットロスの影響を最小化不安定ネットワークでの性能向上

HTTP/3 が TCP の課題を解決する仕組みを示します。

mermaidflowchart LR
    subgraph "TCP (HTTP/2まで)"
        A[順次配送必須] --> B[パケットロス発生]
        B --> C[全体通信停止]
        C --> D[再送待機]
    end

    subgraph "QUIC (HTTP/3)"
        E[ストリーム独立] --> F[パケットロス発生]
        F --> G[該当ストリームのみ影響]
        G --> H[他ストリーム継続]
    end

    style A fill:#ffcccc
    style C fill:#ffcccc
    style E fill:#ccffcc
    style H fill:#ccffcc

HTTP/3 では各ストリームが独立しているため、一部のデータ損失が全体に影響しません。

課題

WebSocket が解決する課題

従来の HTTP ベースの通信では、リアルタイムなアプリケーションを構築する際に以下のような課題がありました。

ポーリングの問題点

javascript// 従来のポーリング方式(非効率的)
function pollServer() {
  setInterval(() => {
    fetch('/api/messages')
      .then((response) => response.json())
      .then((data) => {
        // 新しいメッセージがあるかチェック
        if (data.hasNewMessages) {
          updateUI(data.messages);
        }
      })
      .catch((error) => {
        console.error('ポーリングエラー:', error);
      });
  }, 1000); // 1秒ごとにリクエスト
}

この方式では、新しいデータがなくても定期的にリクエストが送信されるため、サーバーリソースの無駄遣いと不要な通信が発生してしまいます。

WebSocket による解決

javascript// WebSocketを使った効率的な実装
const socket = new WebSocket('ws://localhost:8080');

socket.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  // 新しいデータが来た時のみ処理
  updateUI(data.messages);
});

// サーバーから必要な時のみデータが送られてくる

WebSocket では、サーバー側で変更が発生した際にのみクライアントにデータを送信するため、効率的な通信が実現できます。

HTTP/2・HTTP/3 が解決する課題

HTTP/1.1 の制約

HTTP/1.1 では以下のような問題が顕在化していました。

課題内容影響
ヘッドオブライン阻害1 つのリクエストが遅延すると後続リクエストも待機ページ読み込み速度の低下
接続数制限ブラウザあたり最大 6-8 接続まで並列処理の制限
ヘッダーの冗長性毎回同じヘッダー情報を送信帯域幅の無駄遣い
TCP スロースタート新規接続時の転送速度制限初回アクセスの遅延

HTTP/2・HTTP/3 による改善

javascript// HTTP/1.1時代の並列読み込み対策(非推奨)
function loadResourcesSequentially() {
  return fetch('/api/data1')
    .then((response) => response.json())
    .then((data1) => {
      return fetch('/api/data2');
    })
    .then((response) => response.json())
    .then((data2) => {
      return fetch('/api/data3');
    });
}
javascript// HTTP/2以降では自然に並列処理される
async function loadResourcesParallel() {
  const [data1, data2, data3] = await Promise.all([
    fetch('/api/data1').then((r) => r.json()),
    fetch('/api/data2').then((r) => r.json()),
    fetch('/api/data3').then((r) => r.json()),
  ]);

  return { data1, data2, data3 };
}

HTTP/2 以降では、複数のリクエストが自動的に多重化されるため、開発者は並列処理を意識せずとも高速な通信が実現できます。

従来の HTTP/1.1 の限界

パフォーマンス面での制約

従来の HTTP/1.1 では、モダンな Web アプリケーションの要求に対して以下のような限界がありました。

mermaidflowchart TD
    A[HTTP/1.1の限界] --> B[単一接続での順次処理]
    A --> C[大量のヘッダー送信]
    A --> D[サーバープッシュ非対応]
    A --> E[暗号化のオーバーヘッド]

    B --> F[ページ読み込み遅延]
    C --> G[帯域幅の浪費]
    D --> H[必要リソースの先読み不可]
    E --> I[HTTPS接続の重複コスト]

    style F fill:#ffcccc
    style G fill:#ffcccc
    style H fill:#ffcccc
    style I fill:#ffcccc

これらの制約により、特に多くのリソースを必要とする SPA(Single Page Application)や画像の多い Web サイトでは、顕著な性能劣化が見られるようになりました。

解決策

通信方式の違い

3 つのプロトコルの通信方式には根本的な違いがあります。

通信パターンの比較

mermaidstateDiagram-v2
    state "WebSocket" as WS {
        [*] --> 接続確立
        接続確立 --> 双方向通信
        双方向通信 --> 双方向通信: メッセージ送受信
        双方向通信 --> 接続終了
        接続終了 --> [*]
    }

    state "HTTP/2" as H2 {
        [*] --> リクエスト送信
        リクエスト送信 --> 並列処理
        並列処理 --> レスポンス受信
        レスポンス受信 --> [*]
    }

    state "HTTP/3" as H3 {
        [*] --> QUIC接続
        QUIC接続 --> ストリーム多重化
        ストリーム多重化 --> 高速レスポンス
        高速レスポンス --> [*]
    }

各プロトコルは異なる通信パターンを持ち、それぞれに適した用途があります。

接続の維持方法

WebSocket の接続管理

javascriptclass WebSocketManager {
  constructor(url) {
    this.url = url;
    this.socket = null;
    this.reconnectInterval = 5000;
    this.maxReconnectAttempts = 5;
    this.reconnectAttempts = 0;
  }

  connect() {
    try {
      this.socket = new WebSocket(this.url);
      this.setupEventListeners();
    } catch (error) {
      console.error('WebSocket接続エラー:', error);
      this.handleReconnect();
    }
  }

  setupEventListeners() {
    this.socket.addEventListener('open', () => {
      console.log('WebSocket接続が確立されました');
      this.reconnectAttempts = 0;
    });

    this.socket.addEventListener('close', () => {
      console.log('WebSocket接続が閉じられました');
      this.handleReconnect();
    });

    this.socket.addEventListener('error', (error) => {
      console.error('WebSocketエラー:', error);
    });
  }
}

HTTP/2・HTTP/3 の接続管理

javascript// HTTP/2・HTTP/3では自動的に接続プールが管理される
class HttpClient {
  constructor() {
    // ブラウザが自動的に接続を管理
    this.baseURL = 'https://api.example.com';
  }

  async makeRequest(endpoint) {
    try {
      // HTTP/2・HTTP/3では自動的に最適化される
      const response = await fetch(
        `${this.baseURL}${endpoint}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        }
      );

      return await response.json();
    } catch (error) {
      console.error('HTTP リクエストエラー:', error);
      throw error;
    }
  }
}

WebSocket では開発者が接続の管理を行う必要がありますが、HTTP/2・HTTP/3 ではブラウザが自動的に最適化を行います。

データ転送効率の比較

各プロトコルのデータ転送効率を実際の数値で比較してみましょう。

パフォーマンス比較表

項目WebSocketHTTP/2HTTP/3
接続確立時間一度のみ(50-100ms)リクエストごと(20-50ms)初回のみ(20-30ms)
ヘッダーオーバーヘッド最小(2-14 バイト)圧縮あり(50-200 バイト)さらに圧縮(30-150 バイト)
同時接続数1 接続で双方向100 ストリーム/接続制限なし
再送制御TCPTCPUDP(独自実装)

実際のベンチマーク例

javascript// パフォーマンステスト用のコード例
async function benchmarkProtocols() {
  const testData = {
    messageCount: 1000,
    messageSize: '1KB',
  };

  // WebSocketテスト
  const wsStart = performance.now();
  await testWebSocket(testData);
  const wsTime = performance.now() - wsStart;

  // HTTP/2テスト
  const h2Start = performance.now();
  await testHTTP2(testData);
  const h2Time = performance.now() - h2Start;

  console.log({
    WebSocket: `${wsTime}ms`,
    HTTP2: `${h2Time}ms`,
    efficiency: `WebSocketが${(
      ((h2Time - wsTime) / wsTime) *
      100
    ).toFixed(1)}% 高速`,
  });
}

リアルタイム通信では WebSocket が圧倒的に効率的ですが、RESTful API では HTTP/2・HTTP/3 が優秀な性能を発揮します。

具体例

WebSocket を使うべきケース

WebSocket は、双方向のリアルタイム通信が必要なアプリケーションで真価を発揮します。

チャットアプリケーション

javascript// チャットアプリのWebSocket実装例
class ChatApplication {
  constructor() {
    this.socket = new WebSocket('wss://chat.example.com');
    this.messageQueue = [];
    this.setupEventHandlers();
  }

  setupEventHandlers() {
    this.socket.addEventListener('message', (event) => {
      const message = JSON.parse(event.data);
      this.displayMessage(message);
    });

    this.socket.addEventListener('open', () => {
      // 接続確立後、溜まっていたメッセージを送信
      this.flushMessageQueue();
    });
  }

  sendMessage(text, recipientId) {
    const message = {
      type: 'chat_message',
      content: text,
      recipientId: recipientId,
      timestamp: Date.now(),
      sender: this.userId,
    };

    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(message));
    } else {
      this.messageQueue.push(message);
    }
  }
}

リアルタイムデータ表示(株価モニター)

javascript// 株価リアルタイム表示システム
class StockPriceMonitor {
  constructor() {
    this.socket = new WebSocket(
      'wss://market.example.com/prices'
    );
    this.subscribedStocks = new Set();
    this.priceData = new Map();
  }

  subscribeToStock(symbol) {
    if (!this.subscribedStocks.has(symbol)) {
      this.subscribedStocks.add(symbol);

      const subscription = {
        type: 'subscribe',
        symbol: symbol,
      };

      this.socket.send(JSON.stringify(subscription));
    }
  }

  handlePriceUpdate(data) {
    const { symbol, price, change, volume } = data;

    // UIの更新(リアルタイム)
    this.updatePriceDisplay(symbol, {
      price: price,
      change: change,
      changePercent: (
        (change / (price - change)) *
        100
      ).toFixed(2),
      volume: volume,
      timestamp: new Date(),
    });

    // 価格履歴の保存
    this.priceData.set(symbol, data);
  }
}

オンラインゲーム

javascript// マルチプレイヤーゲームの位置同期
class GameClient {
  constructor(gameId, playerId) {
    this.gameId = gameId;
    this.playerId = playerId;
    this.socket = new WebSocket(
      `wss://game.example.com/game/${gameId}`
    );
    this.playerPositions = new Map();

    this.setupGameHandlers();
  }

  sendPlayerMove(x, y, direction) {
    const moveData = {
      type: 'player_move',
      playerId: this.playerId,
      position: { x, y },
      direction: direction,
      timestamp: Date.now(),
    };

    this.socket.send(JSON.stringify(moveData));
  }

  handleGameUpdate(data) {
    switch (data.type) {
      case 'player_move':
        this.updatePlayerPosition(
          data.playerId,
          data.position
        );
        break;
      case 'game_event':
        this.handleGameEvent(data.event);
        break;
    }
  }
}

HTTP/2・HTTP/3 を使うべきケース

HTTP/2・HTTP/3 は、従来の Web サイトや REST API で優れた性能を発揮します。

REST API サーバー

javascript// Express.js でのHTTP/2対応API
const express = require('express');
const http2 = require('http2');
const fs = require('fs');

const app = express();

// HTTP/2サーバーの設定
const server = http2.createSecureServer({
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem'),
});

server.on('stream', (stream, headers) => {
  // HTTP/2ストリームの処理
  if (
    headers[':path'] === '/api/users' &&
    headers[':method'] === 'GET'
  ) {
    stream.respond({
      'content-type': 'application/json',
      ':status': 200,
    });

    // 大量のユーザーデータを効率的に送信
    const users = getUsersFromDatabase();
    stream.end(JSON.stringify(users));
  }
});

静的リソースの配信

javascript// HTTP/2サーバープッシュの活用
function handleIndexRequest(stream, headers) {
  // HTMLページの送信
  stream.respond({
    'content-type': 'text/html',
    ':status': 200,
  });

  // 必要なリソースを先読みプッシュ
  const pushResources = [
    { path: '/css/main.css', type: 'text/css' },
    { path: '/js/app.js', type: 'application/javascript' },
    { path: '/images/logo.png', type: 'image/png' },
  ];

  pushResources.forEach((resource) => {
    stream.pushStream(
      {
        ':path': resource.path,
        ':method': 'GET',
      },
      (err, pushStream) => {
        if (!err) {
          pushStream.respond({
            'content-type': resource.type,
            ':status': 200,
          });

          const content = fs.readFileSync(
            `./public${resource.path}`
          );
          pushStream.end(content);
        }
      }
    );
  });

  const html = fs.readFileSync('./public/index.html');
  stream.end(html);
}

SPA アプリケーション

javascript// Next.js でのHTTP/3対応(設定例)
// next.config.js
module.exports = {
  experimental: {
    // HTTP/3を有効化
    http3: true,
  },

  // 画像最適化でHTTP/2・HTTP/3の多重化を活用
  images: {
    domains: ['cdn.example.com'],
    formats: ['image/webp', 'image/avif'],
  },

  // APIルートでの効率的なデータ取得
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://backend.example.com/:path*',
      },
    ];
  },
};

実装コード比較

同じ機能を異なるプロトコルで実装した場合の比較を見てみましょう。

リアルタイム通知システムの比較

WebSocket 版(推奨)

javascript// サーバー側(Node.js + ws)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

class NotificationServer {
  constructor() {
    this.clients = new Map();
    this.setupWebSocketServer();
  }

  setupWebSocketServer() {
    wss.on('connection', (ws, request) => {
      const userId = this.extractUserId(request);
      this.clients.set(userId, ws);

      ws.on('close', () => {
        this.clients.delete(userId);
      });
    });
  }

  sendNotification(userId, notification) {
    const client = this.clients.get(userId);
    if (client && client.readyState === WebSocket.OPEN) {
      client.send(
        JSON.stringify({
          type: 'notification',
          data: notification,
          timestamp: Date.now(),
        })
      );
    }
  }
}

HTTP/2 + Server-Sent Events 版(代替案)

javascript// Server-Sent Eventsを使った実装
const express = require('express');
const app = express();

class SSENotificationServer {
  constructor() {
    this.clients = new Map();
    this.setupRoutes();
  }

  setupRoutes() {
    app.get('/notifications/:userId', (req, res) => {
      const userId = req.params.userId;

      res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        Connection: 'keep-alive',
        'Access-Control-Allow-Origin': '*',
      });

      this.clients.set(userId, res);

      req.on('close', () => {
        this.clients.delete(userId);
      });
    });
  }

  sendNotification(userId, notification) {
    const client = this.clients.get(userId);
    if (client) {
      client.write(
        `data: ${JSON.stringify(notification)}\n\n`
      );
    }
  }
}

WebSocket 版では双方向通信が可能ですが、SSE 版では一方向のみとなります。用途に応じて適切な選択が重要です。

まとめ

WebSocket、HTTP/2、HTTP/3 は、それぞれ異なる課題を解決するために開発された通信プロトコルです。適切な選択をすることで、アプリケーションの性能と使用者体験を大きく向上させることができます。

WebSocket は、リアルタイム性が最重要なアプリケーション(チャット、ゲーム、ライブデータ表示)において最適な選択肢です。双方向通信と低レイテンシが実現でき、サーバーリソースの効率的な利用が可能になります。

HTTP/2 は、従来の Web サイトや REST API の性能を向上させたい場合に適しています。多重化とヘッダー圧縮により、特に多くのリソースを必要とするページで顕著な改善が見込めます。

HTTP/3 は、モバイル環境や不安定なネットワークでの通信品質向上に優れています。QUIC プロトコルの採用により、接続確立の高速化とパケットロス耐性の向上が実現されています。

プロトコル選択の際は、アプリケーションの要求事項(リアルタイム性、データ量、ネットワーク環境)を明確にし、それぞれの特性を理解した上で最適な組み合わせを検討することが重要です。また、一つのアプリケーション内で複数のプロトコルを使い分けることも、現実的で効果的なアプローチと言えるでしょう。

関連リンク