WebSocket入門 Part 1 ~Echoサーバーの実装~

はじめに

HTTPの知識はあるが、WebSocketについて一から再度学んでおきたいと思ったため整理することにした。 ここでは、WebSocketの基礎とEchoサーバの実装までをまとめる。
「手紙」と「電話」の違いを例に挙げることで、WebSocketの特性を理解できるようにしてみた。

環境

Node v14以上
ライブラリ ws
ブラウザ Chrome, Edge, Firefox等

HTTPのおさらい

まず、HTTPの特徴を振り返る。
WebSocketHTTP を拡張したプロトコルであるため、基礎知識として押さえておく必要がある。

  • クライアント主導
    クライアント(ブラウザ)からのリクエストがない限り、通信は発生しない。
  • 一方通行
    リクエストとレスポンスの1往復で通信が完了する。
  • ステートレス
    サーバーは過去の通信状態を保持しない。

これを 「手紙」 ✉️ に例えることができる。 用事があるたびに手紙を送り、返事を待つスタイルである。

概念図: HTTP vs WebSocket

HTTP
手紙を送って、返事をもらったら終わりである。

sequenceDiagram
    participant C as Client
    participant S as Server
    
    C->>S: Request (手紙を送る)
    S-->>C: Response (返事が来る)
    Note right of S: ここで関係終了 (切断)

WebSocketとは?

WebSocketは、「双方向通信」 を実現するプロトコルである。

  • サーバープッシュ
    クライアントからのリクエストがなくても、サーバーからデータを送信できる。
  • 持続的接続
    一度接続が確立されると、切断されるまで通信路は維持される。
  • ステートフル
    通信状態を維持し続けることができる。

これを 「電話」 📞 に例えることができる。 一度繋がれば、双方が任意のタイミングで通信可能である。

WebSocket (双方向・持続) 一度電話が繋がれば、どちらからでも話しかけられる。

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: Handshake (電話をかける)
    S-->>C: Accept (電話に出る)
    Note right of S: ずっと繋がっている
    loop 双方向通信
        C->>S: Message (もしもし)
        S->>C: Message (はいはい)
        S->>C: Message (元気?)
    end

接続の仕組み (ハンドシェイク)

WebSocketの接続確立プロセスは、HTTPプロトコルから始まる。

  1. HTTPリクエストによる接続要求
    クライアントが Upgrade: websocket ヘッダーを含むHTTPリクエストを送信する。
  2. サーバーによる承認
    サーバーが 101 Switching Protocols レスポンスを返し、プロトコルの切り替えに合意する。
  3. プロトコルの切り替え
    以降の通信はHTTPではなく、WebSocketプロトコルで行われる。 ※ OSI参照モデルだと、HTTPと同じアプリケーション層に位置する。

この一連の流れを 「ハンドシェイク」 と呼ぶ。

なぜWebSocketを使うのか?

HTTPでもポーリング(定期的にサーバーに問い合わせる)を行えば、擬似的にリアルタイムな通信は可能である。
しかし、WebSocketには以下のメリットがあるため、リアルタイム性が求められる場面で採用される。

  1. 低遅延 (リアルタイム性)
    サーバー側でイベントが発生した瞬間にデータを送信できるため、タイムラグが少ない。
  2. 通信量の削減
    一度接続すれば、HTTPヘッダーのような大きなオーバーヘッドなしでデータをやり取りできる。

利用シーン

  • チャットアプリ: メッセージの即時配信 (LINE, Slack等)
  • オンラインゲーム: プレイヤーの位置情報やアクションの同期
  • リアルタイムダッシュボード: 株価チャート、スポーツの試合速報
  • 共同編集ツール: ドキュメントの同時編集 (Google Docs等)

Echoサーバーの実装

実際にコードを書いて動作を確認してみる。
まずは受信したメッセージをそのまま返す「Echoサーバー」を作成する。

1. プロジェクトの準備

まずは作業用のディレクトリを作成し、必要なパッケージをインストールする。
今回はWebSocketサーバーの実装に ws ライブラリを使用する。

# プロジェクトの初期化
npm init -y

# wsライブラリのインストール
npm install ws

2. サーバー側の実装 (server.js)

Node.jsでWebSocketサーバーを立ち上げるコードとなる。
HTTPサーバーと類似しているが、イベント駆動で通信を制御する点が特徴である。

server.js
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// 'connection' イベント: クライアントからの接続確立時
wss.on('connection', (ws) => {
    console.log('接続しました!');

    // 'message' イベント: クライアントからのメッセージ受信時
    ws.on('message', (message) => {
        console.log(`受信: ${message}`);
        
        // sendメソッド: サーバーからのメッセージ送信
        // HTTPのres.send()とは異なり、任意のタイミングで何度でも実行可能
        ws.send(`Echo: ${message}`);
    });
});

3. クライアント側の実装

クライアント側は、HTMLファイル (index.html) と JavaScriptファイル (client.js) に分けて記述する。

UI部分 (index.html)

index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Echo Test</title>
</head>
<body>
    <h1>WebSocket Echo Test</h1>
    <script src="client.js"></script>
</body>
</html>

ロジック部分 (client.js)

ブラウザ標準の WebSocket API を使用する。

client.js
// client.js
// ws:// プロトコルで接続を開始
const socket = new WebSocket('ws://localhost:8080');

// 接続完了時のイベント
socket.onopen = () => {
    console.log('接続確立');
    // 接続直後にメッセージを送信
    socket.send('Hello Server!');
};

// メッセージ受信時のイベント
socket.onmessage = (event) => {
    console.log(`サーバーから受信: ${event.data}`);
};

4. 動作確認

実装ができたら、実際に動かしてみる。

1. サーバーの起動

ターミナルで以下のコマンドを実行し、サーバーを立ち上げる。

node server.js
# 出力: チャットサーバーが起動しました (ws://localhost:8080)
# (※server.jsにconsole.logを追加している場合)

2. クライアントの接続

作成した index.html をブラウザで開く。
(VS Codeの Open with Live Server や、単にファイルをブラウザにドラッグ&ドロップするだけでもOK)

3. ログの確認

  • ブラウザのコンソール (F12キー → Consoleタブ)

    接続確立
    サーバーから受信: Echo: Hello Server!

    と表示されていれば成功である。

  • サーバー側のターミナル

    接続しました!
    受信: Hello Server!

    と表示され、通信が成立していることが確認できる。

動作原理の解説

  1. 接続時
    ブラウザが Upgrade: websocket ヘッダーを送信し、サーバーが承認することで接続が確立される。
  2. 送信時
    socket.send() によりデータが送信される。HTTPヘッダーが付与されないため軽量な通信となる。
  3. 受信時
    サーバーは接続中のクライアントを認識しているため、即座に ws.send() で応答を返すことができる。

テストアプリの動作シーケンス

今回作成したEchoサーバーの動作フローは以下の通りである。

sequenceDiagram
    participant Browser as Client (Browser)
    participant Node as Server (Node.js)

    Note over Browser, Node: 1. ハンドシェイク
    Browser->>Node: GET / (Upgrade: websocket)
    Node-->>Browser: 101 Switching Protocols

    Note over Browser, Node: 2. 接続確立 (openイベント)
    
    Note over Browser, Node: 3. メッセージ送信
    Browser->>Node: send("Hello Server!")
    Note right of Node: on('message')発火
    
    Note over Browser, Node: 4. Echo
    Node->>Browser: send("Echo: Hello Server!")
    Note left of Browser: onmessage発火

おまけ: ws と socket.io の違い

Node.jsでWebSocketを扱う際、よく比較されるのが socket.io である。 両者の違いを簡単に整理しておく。

  • ws
    WebSocketプロトコルの純粋な実装。標準規格に準拠しており、軽量。ブラウザ標準のAPIで接続できる。
  • socket.io
    リアルタイム通信のためのフレームワーク。自動再接続やルーム機能などが組み込まれている。

今回はWebSocketの仕組みそのものを学習する目的で、純粋な実装であるwsを使用している。
アプリ開発であれば、socket.ioの方が良さそうかも。

参考資料

おわりに

Part 1では、WebSocketの基礎と、単純なEchoサーバーの実装について整理した。 HTTPとの違いや、ハンドシェイクの仕組みを理解することで、WebSocketの全体像が分かってきた気がする。

Part 2では、この知識をベースに、複数人が参加できる「リアルタイムチャットアプリ」の実装をしてみたいと思う。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。