【初心者向け】プログラミングの「ポーリング」vs「プッシュ」

ポーリングとプッシュ通信の違いを初心者向けに解説。それぞれの仕組み、メリット・デメリット、使い分け方を具体例とコードで詳しく紹介します

Learning Next 運営
38 分で読めます

みなさん、Webアプリケーションで「新着メッセージが来たらすぐに画面に表示したい」と思ったことはありませんか?

例えば、チャットアプリやリアルタイム通知など、サーバーからの情報をタイムリーに受け取りたい場面がありますよね。

そんな時に重要になるのが「ポーリング」と「プッシュ」という2つの通信方式です。 この記事では、プログラミング初心者の方でも理解できるように、それぞれの仕組みと使い分け方を詳しく解説します。

ポーリングとプッシュの基本概念

ポーリングとは?

ポーリングとは、クライアント側が定期的にサーバーに「新しい情報はありますか?」と問い合わせる方式です。

簡単に言うと、「こちらから聞きに行く」方法です。

日常生活での例

郵便受けの確認

  • 毎日決まった時間に郵便受けを確認する
  • 手紙が来ているかどうかを自分でチェック
  • 手紙がなくても確認作業は発生する

これがポーリングのイメージです。

プッシュとは?

プッシュとは、サーバー側が新しい情報が発生した時に、クライアントに能動的に送信する方式です。

簡単に言うと、「向こうから教えてくれる」方法です。

日常生活での例

宅配便の配達

  • 荷物が届いた時にインターホンが鳴る
  • 配達員が積極的に知らせてくれる
  • 荷物がない時は何も起こらない

これがプッシュのイメージです。

基本的な違い

## ポーリング vs プッシュ
| 項目 | ポーリング | プッシュ |
|------|------------|----------|
| 通信の開始者 | クライアント | サーバー |
| タイミング | 定期的 | 必要な時のみ |
| 無駄な通信 | 発生しやすい | 少ない |
| 実装の複雑さ | 簡単 | やや複雑 |
| リアルタイム性 | 遅延あり | 即座 |

ポーリングの詳細解説

基本的な仕組み

シンプルなポーリングの例

// 基本的なポーリングの実装
function startPolling() {
setInterval(async () => {
try {
// サーバーに新着メッセージを問い合わせ
const response = await fetch('/api/messages/new');
const newMessages = await response.json();
if (newMessages.length > 0) {
// 新着メッセージがあれば画面に表示
displayNewMessages(newMessages);
}
console.log('ポーリング完了:', new Date());
} catch (error) {
console.error('ポーリングエラー:', error);
}
}, 5000); // 5秒ごとに確認
}
function displayNewMessages(messages) {
const messageList = document.getElementById('messageList');
messages.forEach(message => {
const messageElement = document.createElement('div');
messageElement.textContent = message.content;
messageList.appendChild(messageElement);
});
}
// ポーリング開始
startPolling();

サーバー側の実装例

# Flask でのサーバー側実装
from flask import Flask, jsonify
from datetime import datetime
app = Flask(__name__)
# 新着メッセージを保存するリスト
new_messages = []
last_check_times = {}
@app.route('/api/messages/new')
def get_new_messages():
client_id = request.args.get('client_id')
last_check = last_check_times.get(client_id, datetime.min)
# 最後にチェックした時刻以降のメッセージを取得
recent_messages = [
msg for msg in new_messages
if msg['timestamp'] > last_check
]
# 最終チェック時刻を更新
last_check_times[client_id] = datetime.now()
return jsonify(recent_messages)
@app.route('/api/messages', methods=['POST'])
def add_message():
message = request.json
message['timestamp'] = datetime.now()
new_messages.append(message)
return jsonify({'status': 'success'})

ポーリングの種類

1. 短いポーリング(Short Polling)

特徴

  • サーバーは即座にレスポンスを返す
  • 新しいデータがなくても空のレスポンス
  • 実装が簡単
// 短いポーリングの例
async function shortPolling() {
const response = await fetch('/api/status');
const data = await response.json();
if (data.hasUpdate) {
updateUI(data);
}
// すぐに次のポーリングをスケジュール
setTimeout(shortPolling, 3000);
}

2. 長いポーリング(Long Polling)

特徴

  • サーバーは新しいデータが来るまで待機
  • 効率的な通信
  • 実装がやや複雑
// 長いポーリングの例
async function longPolling() {
try {
// サーバーは新しいデータが来るまで待機
const response = await fetch('/api/wait-for-update', {
timeout: 30000 // 30秒でタイムアウト
});
const data = await response.json();
updateUI(data);
} catch (error) {
console.log('タイムアウトまたはエラー');
}
// 即座に次の長いポーリングを開始
longPolling();
}

サーバー側の長いポーリング

import time
from flask import Flask, jsonify
@app.route('/api/wait-for-update')
def wait_for_update():
timeout = 30 # 30秒でタイムアウト
start_time = time.time()
while time.time() - start_time < timeout:
# 新しいデータがあるかチェック
if has_new_data():
return jsonify(get_new_data())
# 1秒待ってから再チェック
time.sleep(1)
# タイムアウト時は空のレスポンス
return jsonify({'timeout': True})

ポーリングのメリット・デメリット

メリット

シンプルな実装

  • HTTP の標準的な仕組みを使用
  • ファイアウォールやプロキシに優しい
  • デバッグが容易

信頼性

  • ネットワーク障害からの回復が簡単
  • サーバーの再起動があっても自動的に再開

デメリット

効率性の問題

## 無駄な通信の例
- 5秒ごとにポーリング
- 新着メッセージは1時間に1回
- 720回中719回が無駄な通信

遅延の発生

## 遅延の計算
ポーリング間隔: 5秒
平均遅延: 2.5秒(最大5秒)
リアルタイム性: 低い

サーバー負荷

  • 多数のクライアントが同時にポーリング
  • サーバーリソースの消費
  • データベースへの頻繁なアクセス

プッシュ通信の詳細解説

基本的な仕組み

WebSocketを使ったプッシュ通信

// WebSocket クライアント側の実装
const socket = new WebSocket('ws://localhost:8080');
// 接続が開かれた時
socket.onopen = function(event) {
console.log('WebSocket接続が開かれました');
// サーバーに認証情報を送信
socket.send(JSON.stringify({
type: 'auth',
userId: 'user123'
}));
};
// メッセージを受信した時
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
switch(data.type) {
case 'newMessage':
displayNewMessage(data.message);
break;
case 'notification':
showNotification(data.content);
break;
case 'userOnline':
updateUserStatus(data.userId, 'online');
break;
}
};
// 接続が閉じられた時
socket.onclose = function(event) {
console.log('WebSocket接続が閉じられました');
// 再接続の試行
setTimeout(connectWebSocket, 5000);
};
// エラーが発生した時
socket.onerror = function(error) {
console.error('WebSocketエラー:', error);
};
// メッセージの送信
function sendMessage(message) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({
type: 'message',
content: message
}));
}
}

サーバー側の実装(Node.js + WebSocket)

const WebSocket = require('ws');
const http = require('http');
// HTTPサーバーの作成
const server = http.createServer();
// WebSocketサーバーの作成
const wss = new WebSocket.Server({ server });
// 接続中のクライアントを管理
const clients = new Map();
wss.on('connection', function connection(ws) {
console.log('新しいクライアントが接続しました');
ws.on('message', function incoming(data) {
const message = JSON.parse(data);
switch(message.type) {
case 'auth':
// クライアントを認証・登録
clients.set(message.userId, ws);
console.log(`ユーザー ${message.userId} が認証されました`);
break;
case 'message':
// 全クライアントにメッセージをブロードキャスト
broadcastMessage({
type: 'newMessage',
message: message.content,
timestamp: new Date().toISOString()
});
break;
}
});
ws.on('close', function close() {
// クライアントの削除
for (let [userId, client] of clients) {
if (client === ws) {
clients.delete(userId);
console.log(`ユーザー ${userId} が切断しました`);
break;
}
}
});
});
// 全クライアントにメッセージを送信
function broadcastMessage(message) {
clients.forEach((client, userId) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
}
// 特定のユーザーにメッセージを送信
function sendToUser(userId, message) {
const client = clients.get(userId);
if (client && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
}
server.listen(8080, () => {
console.log('WebSocketサーバーがポート8080で起動しました');
});

プッシュ通信の種類

1. WebSocket

特徴

  • 双方向の通信が可能
  • 低遅延
  • 持続的な接続

使用例

// リアルタイムチャット
socket.onmessage = function(event) {
const message = JSON.parse(event.data);
addMessageToChat(message);
};
// ライブコメント
socket.onmessage = function(event) {
const comment = JSON.parse(event.data);
displayLiveComment(comment);
};
// リアルタイム共同編集
socket.onmessage = function(event) {
const edit = JSON.parse(event.data);
applyEditToDocument(edit);
};

2. Server-Sent Events (SSE)

特徴

  • サーバーからクライアントへの一方向通信
  • HTTP/HTTPSを使用
  • 自動再接続機能
// SSE クライアント側
const eventSource = new EventSource('/api/stream');
// メッセージ受信
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
updateUI(data);
};
// 特定のイベント受信
eventSource.addEventListener('notification', function(event) {
const notification = JSON.parse(event.data);
showNotification(notification);
});
// エラーハンドリング
eventSource.onerror = function(error) {
console.error('SSEエラー:', error);
};

SSE サーバー側(Express.js)

const express = require('express');
const app = express();
app.get('/api/stream', (req, res) => {
// SSE ヘッダーの設定
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
// 定期的にデータを送信
const interval = setInterval(() => {
const data = {
timestamp: new Date().toISOString(),
message: 'Hello from server'
};
res.write(`data: ${JSON.stringify(data)}
`);
}, 5000);
// 接続が閉じられた時の処理
req.on('close', () => {
clearInterval(interval);
console.log('SSE接続が閉じられました');
});
});

3. Push API (ブラウザ通知)

特徴

  • ブラウザが閉じていても通知可能
  • サービスワーカーを使用
  • モバイル対応
// サービスワーカー登録
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker登録成功');
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
});
})
.then(subscription => {
// サブスクリプション情報をサーバーに送信
return fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
});
}
// サービスワーカー (sw.js)
self.addEventListener('push', function(event) {
const data = event.data.json();
const options = {
body: data.message,
icon: '/icon-192x192.png',
badge: '/badge-72x72.png',
data: data.url
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});

プッシュ通信のメリット・デメリット

メリット

効率性

  • 必要な時のみ通信
  • サーバーリソースの節約
  • ネットワーク帯域の効率利用

リアルタイム性

  • 即座にデータを受信
  • 遅延が最小限
  • ユーザー体験の向上

スケーラビリティ

  • 多数のクライアントに効率的に配信
  • ブロードキャスト機能

デメリット

実装の複雑さ

  • 接続管理が必要
  • エラーハンドリングが複雑
  • 再接続ロジックの実装

ネットワーク制約

  • ファイアウォールで制限される場合
  • プロキシサーバーの影響
  • モバイルネットワークでの制限

使い分けのガイドライン

ポーリングが適している場面

1. データ更新頻度が低い場合

例: ダッシュボードの統計データ

// 1時間ごとの売上データ更新
setInterval(async () => {
const salesData = await fetch('/api/sales/hourly');
const data = await salesData.json();
updateSalesChart(data);
}, 60000); // 1分ごとにチェック(十分な頻度)

2. シンプルな実装が必要な場合

例: 簡単な通知システム

// 新着通知の確認
async function checkNotifications() {
const response = await fetch('/api/notifications');
const notifications = await response.json();
notifications.forEach(notification => {
if (!notification.read) {
showNotification(notification);
}
});
}
// 30秒ごとにチェック
setInterval(checkNotifications, 30000);

3. 一時的な監視が必要な場合

例: ファイルアップロードの進捗確認

// アップロード進捗の監視
async function monitorUpload(uploadId) {
const checkProgress = async () => {
const response = await fetch(`/api/upload/${uploadId}/progress`);
const progress = await response.json();
updateProgressBar(progress.percentage);
if (progress.completed) {
onUploadComplete();
} else {
setTimeout(checkProgress, 1000);
}
};
checkProgress();
}

プッシュが適している場面

1. リアルタイム性が重要な場合

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

// 即座にメッセージを受信
socket.onmessage = function(event) {
const message = JSON.parse(event.data);
if (message.type === 'chat') {
addMessageToChat({
user: message.sender,
content: message.content,
timestamp: new Date(message.timestamp)
});
// 音で通知
playNotificationSound();
}
};

2. 高頻度でのデータ更新

例: 株価のリアルタイム表示

// 株価の瞬時更新
socket.onmessage = function(event) {
const priceUpdate = JSON.parse(event.data);
if (priceUpdate.type === 'priceChange') {
updateStockPrice(priceUpdate.symbol, priceUpdate.price);
highlightPriceChange(priceUpdate.symbol, priceUpdate.change);
}
};

3. 多数のクライアントへの同報

例: ライブイベントの配信

// ライブイベントの情報配信
socket.onmessage = function(event) {
const eventData = JSON.parse(event.data);
switch(eventData.type) {
case 'liveComment':
addLiveComment(eventData.comment);
break;
case 'viewerCount':
updateViewerCount(eventData.count);
break;
case 'eventEnd':
showEventEndMessage();
break;
}
};

ハイブリッド手法

ポーリング + プッシュの組み合わせ

class HybridCommunication {
constructor() {
this.websocket = null;
this.pollingInterval = null;
this.isWebSocketConnected = false;
}
start() {
// WebSocket接続を試行
this.connectWebSocket();
// フォールバックとしてポーリングを開始
this.startPolling();
}
connectWebSocket() {
this.websocket = new WebSocket('ws://localhost:8080');
this.websocket.onopen = () => {
console.log('WebSocket接続成功');
this.isWebSocketConnected = true;
// WebSocket接続成功時はポーリングを停止
this.stopPolling();
};
this.websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.websocket.onclose = () => {
console.log('WebSocket接続断');
this.isWebSocketConnected = false;
// 接続断時はポーリングにフォールバック
this.startPolling();
// 再接続を試行
setTimeout(() => this.connectWebSocket(), 5000);
};
}
startPolling() {
if (this.pollingInterval) return;
console.log('ポーリング開始');
this.pollingInterval = setInterval(async () => {
if (!this.isWebSocketConnected) {
const response = await fetch('/api/messages');
const data = await response.json();
this.handleMessage(data);
}
}, 5000);
}
stopPolling() {
if (this.pollingInterval) {
console.log('ポーリング停止');
clearInterval(this.pollingInterval);
this.pollingInterval = null;
}
}
handleMessage(data) {
// メッセージの処理
console.log('受信:', data);
updateUI(data);
}
}
// 使用例
const communication = new HybridCommunication();
communication.start();

実装時の注意点

ポーリング実装のベストプラクティス

1. 適切な間隔の設定

class AdaptivePolling {
constructor() {
this.baseInterval = 5000; // 基本間隔: 5秒
this.maxInterval = 60000; // 最大間隔: 60秒
this.currentInterval = this.baseInterval;
this.consecutiveEmptyResponses = 0;
}
async poll() {
try {
const response = await fetch('/api/updates');
const data = await response.json();
if (data.hasUpdates) {
// データありの場合は間隔をリセット
this.currentInterval = this.baseInterval;
this.consecutiveEmptyResponses = 0;
this.handleUpdates(data);
} else {
// データなしの場合は間隔を徐々に伸ばす
this.consecutiveEmptyResponses++;
this.currentInterval = Math.min(
this.baseInterval * Math.pow(1.5, this.consecutiveEmptyResponses),
this.maxInterval
);
}
} catch (error) {
console.error('ポーリングエラー:', error);
// エラー時は間隔を長くする
this.currentInterval = Math.min(this.currentInterval * 2, this.maxInterval);
}
// 次のポーリングをスケジュール
setTimeout(() => this.poll(), this.currentInterval);
}
start() {
this.poll();
}
}

2. エラーハンドリング

class RobustPolling {
constructor() {
this.retryCount = 0;
this.maxRetries = 3;
this.isActive = true;
}
async poll() {
if (!this.isActive) return;
try {
const response = await fetch('/api/data', {
timeout: 10000 // 10秒でタイムアウト
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// 成功時はリトライカウントをリセット
this.retryCount = 0;
this.handleSuccess(data);
} catch (error) {
this.handleError(error);
}
// 次のポーリングをスケジュール
this.scheduleNext();
}
handleError(error) {
console.error('ポーリングエラー:', error);
this.retryCount++;
if (this.retryCount >= this.maxRetries) {
console.error('最大リトライ回数に達しました');
this.isActive = false;
this.onMaxRetriesReached();
}
}
scheduleNext() {
if (this.isActive) {
const delay = this.retryCount > 0
? 5000 * Math.pow(2, this.retryCount) // 指数バックオフ
: 1000; // 通常間隔
setTimeout(() => this.poll(), delay);
}
}
}

プッシュ通信実装のベストプラクティス

1. 再接続の実装

class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
this.reconnectDecay = 1.5;
this.timeoutInterval = 2000;
this.maxReconnectAttempts = null;
this.reconnectAttempts = 0;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = (event) => {
console.log('WebSocket接続成功');
this.reconnectAttempts = 0;
this.reconnectDelay = 1000;
this.onopen(event);
};
this.ws.onmessage = (event) => {
this.onmessage(event);
};
this.ws.onclose = (event) => {
console.log('WebSocket接続閉じる');
this.onclose(event);
this.scheduleReconnect();
};
this.ws.onerror = (event) => {
console.error('WebSocketエラー:', event);
this.onerror(event);
};
}
scheduleReconnect() {
if (this.maxReconnectAttempts &&
this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('最大再接続試行回数に達しました');
return;
}
this.reconnectAttempts++;
setTimeout(() => {
console.log(`再接続試行 ${this.reconnectAttempts}`);
this.connect();
}, this.reconnectDelay);
// 遅延時間を増加
this.reconnectDelay = Math.min(
this.reconnectDelay * this.reconnectDecay,
this.maxReconnectDelay
);
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.warn('WebSocket接続が利用できません');
}
}
// イベントハンドラ(オーバーライド可能)
onopen(event) {}
onmessage(event) {}
onclose(event) {}
onerror(event) {}
}

2. メッセージキューイング

class QueuedWebSocket {
constructor(url) {
this.url = url;
this.messageQueue = [];
this.isConnected = false;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('接続成功');
this.isConnected = true;
// キューに蓄積されたメッセージを送信
this.flushQueue();
};
this.ws.onclose = () => {
console.log('接続断');
this.isConnected = false;
};
this.ws.onmessage = (event) => {
this.handleMessage(event.data);
};
}
send(message) {
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
// 接続していない場合はキューに追加
this.messageQueue.push(message);
console.log('メッセージをキューに追加:', message);
}
}
flushQueue() {
console.log(`${this.messageQueue.length}件のメッセージを送信`);
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.ws.send(JSON.stringify(message));
}
}
handleMessage(data) {
const message = JSON.parse(data);
console.log('受信:', message);
}
}

まとめ

ポーリングとプッシュは、それぞれ異なる特徴と適用場面を持つ通信方式です。

重要なポイント

  • ポーリング: シンプルで信頼性が高いが、効率性に課題
  • プッシュ: 効率的でリアルタイムだが、実装が複雑
  • 使い分け: 要件に応じた適切な選択が重要
  • ハイブリッド: 両方の利点を活かした組み合わせも有効

初心者の方は、まずポーリングから始めて、必要に応じてプッシュ通信を導入することをおすすめします。

どちらの方式も理解して適切に使い分けることで、ユーザーにとって快適なWebアプリケーションを作ることができるでしょう。 ぜひ実際のプロジェクトで試してみてください。

関連記事