双方向通信/リアルタイム通信について
はじめに
近年の Web アプリケーションでは、チャット、通知、株価やセンサー情報のリアルタイム表示など、「画面をリロードしなくても、サーバー側の変化が即座に反映されてほしい」という要件が当たり前になってきている。
しかし、もともとの HTTP は「クライアントがリクエストを送り、サーバーがレスポンスを返して終わり」という一問一答型の通信であり、サーバーから勝手に話しかけることはできない。この前提のままでは、リアルタイムなアプリケーションを作るのは難しい。
そこで登場するのが「双方向通信」である。本記事では、
-
そもそも双方向通信とは何か
-
どんなときに必要になるのか
-
双方向通信(あるいはそれに近い体験)を実現するための代表的な技術
- Polling
- Long Polling
- Server-Sent Events (SSE)
- WebSocket
- WebRTC
を、それぞれの特徴・向き不向きとともに整理していく。
1. 双方向通信とは何か
ここでは Web アプリの文脈で「双方向通信」をざっくりと次のように捉える。
クライアントとサーバーが、お互いに必要なタイミングでメッセージを送り合える状態
通常の HTTP リクエスト/レスポンスは、クライアントからのリクエストがない限りサーバーはデータを送れない。つまり、
- クライアント → サーバー:いつでも送れる
- サーバー → クライアント:クライアントからのリクエストがあったときだけ
という「片方向寄り」のモデルである。
一方で双方向通信では、
- クライアント → サーバー:クライアントのタイミングで送信できる
- サーバー → クライアント:サーバー側のイベント発生タイミングで送信できる
という状態を目指す。この「サーバーから任意タイミングで送れる」という要素が、リアルタイム性の鍵になる。
2. 双方向通信はいつ必要になるのか
双方向通信(あるいはサーバープッシュ)が必要になるのは、主に次のような場面である。
-
チャット・メッセージング
- 相手からのメッセージが届いた瞬間に自分の画面にも反映したい
-
通知・アラート
- 在庫切れ、注文状態の変化、監視アラートなどを即座にユーザーへ伝えたい
-
メディア配信
- ユーザー間の音声・映像を、低遅延でやり取りしたい
3. 双方向通信を支える代表的な技術
ここからは、Web で双方向通信(あるいはそれに近い体験)を実現するための代表的な技術を順番に見ていく。
- Polling(ポーリング)
- Long Polling(ロングポーリング)
- Server-Sent Events(SSE)
- WebSocket
- WebRTC
4. Polling(ポーリング)
4-1. 仕組み
Polling は最も単純な「なんちゃってリアルタイム通信」である。クライアントが一定間隔でサーバーに対して
「何か新しいデータはあるか?」
と HTTP リクエストを送り、あればレスポンスで受け取る、という方式である。
擬似コードで書くとこんなイメージである。
async function poll() {
const res = await fetch("/api/messages/latest");
const data = await res.json();
render(data);
// 5秒後にまた問い合わせる
setTimeout(poll, 5000);
}
poll();
4-2. 双方向性の度合い
- クライアント → サーバー:通常の HTTP リクエストなので問題なく送れる
- サーバー → クライアント:クライアントが問い合わせたタイミングでしか届かない
サーバーから任意タイミングでプッシュはできないので、厳密な意味では双方向通信ではないが、「それっぽい体験」を簡単に作れる。
4-3. メリット
- 仕組みが単純で実装しやすい
- 既存の HTTP サーバーのままで動かせることが多い
- プロキシやロードバランサーとの相性問題が少ない
4-4. デメリット
- 更新がないときも一定間隔でリクエストを送り続けるため無駄が多い
- ポーリング間隔より細かいリアルタイム性は出せない(例: 5 秒間隔なら、最大 5 秒の遅延)
4-5. 向いているケース
- 少し遅延してもよいステータス更新
- 開発初期のプロトタイピング
- トラフィック量やユーザー数がそこまで多くない小規模サービス
5. Long Polling(ロングポーリング)
5-1. 仕組み
Long Polling は Polling の無駄を減らすための発展系である。
- クライアントがサーバーにリクエストを送る
- サーバーはすぐにはレスポンスを返さず、新しいイベントが発生するまで接続を保持する
- イベントが発生したらレスポンスを返す
- クライアントはレスポンスを受け取ると、またすぐ次のリクエストを投げる
こうすることで、「イベントが起きた瞬間に近いタイミングでクライアントへ返す」ことができる。
5-2. 双方向性の度合い
- クライアント → サーバー:通常のリクエストで送れる
- サーバー → クライアント:クライアントがリクエスト中であれば、イベント発生タイミングでレスポンスを返せる
限りなくサーバープッシュに近いが、接続はイベントごとに切れて再接続されるという点が、後述の WebSocket との違いである。
5-3. メリット
- Polling より無駄なリクエストが減る
- ブラウザ/サーバー/プロキシの対応状況が良く、レガシー環境でも動きやすい
- 実装難易度も比較的低い
5-4. デメリット
- イベントが頻繁に発生すると、結局リクエスト数は多くなる
- 接続を長時間保持するため、サーバーのコネクションリソースを消費しやすい
- 厳密にはフルな双方向通信ではなく、依然として「疑似リアルタイム」
5-5. 向いているケース
- 比較的リアルタイム性が必要だが、WebSocket を導入するほどではない場合
- 既存のインフラ・ライブラリが Long Polling を前提としている場合
6. Server-Sent Events(SSE)
6-1. 仕組み
Server-Sent Events(SSE)は、HTTP 接続を使ってサーバーからクライアントへの一方向ストリーミングを行う仕組みである。
クライアントは EventSource というブラウザの API を使い、サーバーの URL に接続する。
const source = new EventSource("/events");
source.onmessage = (event) => {
const data = JSON.parse(event.data);
render(data);
};
サーバーは text/event-stream という MIME タイプで、テキストのイベントをストリームとして流し続けることで、クライアントにプッシュできる。
6-2. 双方向性の度合い
- サーバー → クライアント:イベントをプッシュ可能(リアルタイム性あり)
- クライアント → サーバー:別途通常の HTTP リクエストが必要
つまり、SSE 自体は「一方向通信(サーバー → クライアント)」であり、狭義の双方向通信ではない。ただし、
- 下り(サーバー → クライアント)は SSE
- 上り(クライアント → サーバー)は通常の
fetch/POST
という組み合わせで、実用上の双方向的な挙動を作ることは可能である。
6-3. メリット
- 実装がシンプルで、ブラウザ側の API も分かりやすい
- HTTP ベースなのでプロキシやロードバランサーとの相性が良いことが多い
- 自動再接続などの機能が仕様として用意されている
6-4. デメリット
- 真の意味での双方向通信ではなく、「サーバー → クライアント」のみ
- バイナリ送信は基本的にテキストベースの扱いになる
- 常時接続のため、サーバーリソースの管理が必要
6-5. 向いているケース
- 株価、ニュースフィード、通知、ログビューアなど「サーバーからの一方向更新」が中心の UI
- WebSocket を使うほど複雑なプロトコルは要らないが、プッシュ更新は欲しい場合
7. WebSocket
7-1. 仕組み
WebSocket は、クライアントとサーバー間で フルデュプレックス(双方向同時通信) を行うための専用プロトコルである。
- 最初は通常の HTTP リクエストとして接続し、
Upgrade: websocketヘッダーでプロトコル変更を要求する - サーバーが
101 Switching Protocolsで応答すると、同じ TCP コネクション上で WebSocket プロトコルに切り替わる - 以降は、クライアント・サーバー双方が任意タイミングでメッセージ(フレーム)を送信できる
7-2. 双方向性の度合い
-
クライアント ↔ サーバー:どちらからでも、いつでもメッセージを送れる
- 真の意味での双方向通信(フルデュプレックス)
7-3. シンプルなクライアントコード例(TypeScript)
const socket = new WebSocket("wss://example.com/chat");
socket.onopen = () => {
console.log("connected");
socket.send(JSON.stringify({ type: "join", roomId: "general" }));
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("message from server:", data);
};
socket.onclose = () => {
console.log("disconnected");
};
7-4. メリット
- 一度接続すれば、メッセージ送受信のたびに HTTP ハンドシェイクが不要で効率的
- 低遅延でリアルタイム性が高い
- テキスト・バイナリの両方に対応
- 双方向のやり取りが自然に書けるため、チャット・ゲーム・共同編集などと相性が良い
7-5. デメリット
- 常時接続のため、接続数が増えるとサーバーリソース管理が難しくなる
- 負荷分散、スケールアウト、再接続戦略など、設計上の考慮点が多い
- 一部の企業ネットワークや古いプロキシ環境では制限を受ける可能性がある
7-6. 向いているケース
- チャットアプリケーション
- オンラインゲーム
- 協調編集ツール
- 双方向での頻繁なイベントのやり取りが必要なリアルタイムアプリ全般
8. WebRTC
8-1. 仕組み
WebRTC は、ブラウザ間(ピア間)のリアルタイム音声・映像・データ通信を行うための仕組みである。主な目的はメディア伝送だが、DataChannel によって任意データもやり取りできる。
このとき、シグナリング(接続情報の交換)には別途 HTTP や WebSocket などが使われるため、WebRTC 単体で完結するわけではない。
8-2. 双方向性の度合い
- ブラウザ ↔ ブラウザ(あるいはネイティブアプリ):音声・映像・データを双方向に送受信可能
- エンドユーザー同士の直接通信に特化しており、サーバーとクライアントという構図とは少し異なる
8-3. メリット
- 音声・映像などのリアルタイムメディアに最適
- P2P を前提としており、遅延を最小化しやすい
8-4. デメリット
- シグナリング、NAT 越え、TURN サーバーなど学ぶべきコンポーネントが多い
- 単純な「サーバー ↔ クライアント」通信用にはオーバースペックなことが多い
8-5. 向いているケース
- ビデオ会議
- 音声チャット
- P2P を活用したゲームやデータ同期
9. どの技術を選ぶべきか
最後に、用途ごとにざっくりとした選び方の指針をまとめる。
-
まずは Polling / Long Polling で十分かを考える
- 「数秒遅れてもいい」「トラフィックもユーザー数も少ない」なら、実装と運用が簡単な方がよい
-
サーバー → クライアントの一方向プッシュが中心なら SSE
- 株価、通知、ログ、メトリクスなど、「流してくる情報を眺める」タイプの UI
-
ガチな双方向リアルタイム通信が必要なら WebSocket
- チャット、ゲーム、協調編集など、「双方から頻繁にメッセージが飛び交う」場面
-
音声・映像・P2P が主役なら WebRTC
- メディア伝送やブラウザ間通信が本丸なら、WebRTC を検討する
参考資料
Discussion
読み書き用に stream を2本使ってやってもできそうですね。( javascript だと 未完了のストリームを fetch で送信して逐次書き込む手法が使える為