リアルタイムアプリケーションにおいて、WebSocket接続の安定性はユーザー体験の根幹を成します。本稿では、切断検知から自動再接続、エクスポネンシャルバックオフ,再到軍処理まで、堅牢なWebSocket通信を実装するための包括的なガイドをお届けします。

HolySheep AIでは、今すぐ登録して<50msの超低レイテンシ環境を体験でき、最初の無料クレジットと共に始められます。

WebSocket接続断开重连机制实现

1. コスト比較:なぜHolySheep AIが最適か

まず、API利用コストの現実的な数字を確認しましょう。2026年現在のoutput価格 (/MTok) を比較します:

モデル価格 ($/MTok)月間1000万トークン
GPT-4.1$8.00$80,000
Claude Sonnet 4.5$15.00$150,000
Gemini 2.5 Flash$2.50$25,000
DeepSeek V3.2$0.42$4,200

HolySheep AIでは、レートが¥1=$1(公式¥7.3=$1比85%節約)という破格の条件を提供しており、DeepSeek V3.2と同等のコストパフォーマンスで、より安定した接続環境を利用できます。

2. 基本的な再接続マネージャー

まず、接続状態管理と自動再接続を実装した基本的なクラスを紹介します。

class WebSocketReconnectManager {
    private ws: WebSocket | null = null;
    private url: string;
    private reconnectAttempts = 0;
    private maxReconnectAttempts = 10;
    private baseDelay = 1000; // 1秒
    private maxDelay = 30000; // 30秒
    private reconnectTimer: number | null = null;
    private heartbeatTimer: number | null = null;
    private isManualClose = false;
    
    // HolySheep API設定
    private readonly baseUrl = 'https://api.holysheep.ai/v1';
    private apiKey: string;

    constructor(url: string, apiKey: string) {
        this.url = url;
        this.apiKey = apiKey;
    }

    connect(): void {
        this.isManualClose = false;
        this.reconnectAttempts = 0;
        
        try {
            this.ws = new WebSocket(this.url);
            this.setupEventHandlers();
            this.startHeartbeat();
        } catch (error) {
            console.error('WebSocket接続エラー:', error);
            this.scheduleReconnect();
        }
    }

    private setupEventHandlers(): void {
        if (!this.ws) return;

        this.ws.onopen = () => {
            console.log('✅ WebSocket接続確立');
            this.reconnectAttempts = 0;
            this.startHeartbeat();
        };

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMessage(data);
        };

        this.ws.onerror = (error) => {
            console.error('❌ WebSocketエラー:', error);
        };

        this.ws.onclose = (event) => {
            console.log(🔌 切断: code=${event.code}, reason=${event.reason});
            this.cleanup();
            
            if (!this.isManualClose) {
                this.scheduleReconnect();
            }
        };
    }

    private calculateBackoff(): number {
        // エクスポネンシャルバックオフ + ジッター
        const exponentialDelay = Math.min(
            this.baseDelay * Math.pow(2, this.reconnectAttempts),
            this.maxDelay
        );
        const jitter = Math.random() * 1000;
        return exponentialDelay + jitter;
    }

    private scheduleReconnect(): void {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('最大再接続試行回数に達しました');
            return;
        }

        const delay = this.calculateBackoff();
        this.reconnectAttempts++;
        
        console.log(⏳ ${delay}ms後に再接続を試行 (${this.reconnectAttempts}/${this.maxReconnectAttempts}));
        
        this.reconnectTimer = window.setTimeout(() => {
            this.connect();
        }, delay);
    }

    private startHeartbeat(): void {
        this.heartbeatTimer = window.setInterval(() => {
            if (this.ws?.readyState === WebSocket.OPEN) {
                this.ws.send(JSON.stringify({ type: 'ping' }));
            }
        }, 30000); // 30秒ごとにping
    }

    private handleMessage(data: any): void {
        if (data.type === 'pong') {
            console.log('💓 心拍確認');
            return;
        }
        
        // メッセージ処理のロジック
        console.log('📨 メッセージ受信:', data);
    }

    private cleanup(): void {
        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
            this.reconnectTimer = null;
        }
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
    }

    disconnect(): void {
        this.isManualClose = true;
        this.cleanup();
        this.ws?.close();
        this.ws = null;
    }

    send(data: any): void {
        if (this.ws?.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(data));
        } else {
            console.warn('⚠️ 接続が確立されていません');
        }
    }
}

// 使用例
const manager = new WebSocketReconnectManager(
    'wss://api.holysheep.ai/v1/stream',
    'YOUR_HOLYSHEEP_API_KEY'
);
manager.connect();

3. ストリーミングAPIとの統合実装

HolySheep AIのリアルタイムAPIを活用した、完整的ストリーミング実装例を示します。

class HolySheepStreamingClient {
    private baseUrl = 'https://api.holysheep.ai/v1';
    private apiKey: string;
    private ws: WebSocket | null = null;
    private messageQueue: any[] = [];
    private isConnected = false;
    private connectionListeners: ((connected: boolean) => void)[] = [];
    
    // 再接続設定
    private reconnectConfig = {
        maxRetries: 5,
        currentRetries: 0,
        baseInterval: 1000,
        maxInterval: 16000
    };

    constructor(apiKey: string) {
        this.apiKey = apiKey;
        this.setupVisibilityHandler(); // ページ可視性変化の監視
    }

    private setupVisibilityHandler(): void {
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible' && !this.isConnected) {
                console.log('👁️ ページが可視化されました。再接続を試行...');
                this.reconnect();
            }
        });
    }

    async connect(): Promise {
        return new Promise((resolve, reject) => {
            try {
                const wsUrl = ${this.baseUrl.replace('http', 'ws')}/websocket?api_key=${this.apiKey};
                this.ws = new WebSocket(wsUrl);

                this.ws.onopen = () => {
                    console.log('🎯 HolySheep AIに接続完了');
                    this.isConnected = true;
                    this.reconnectConfig.currentRetries = 0;
                    this.flushQueue();
                    this.notifyListeners(true);
                    resolve();
                };

                this.ws.onmessage = (event) => {
                    const data = JSON.parse(event.data);
                    this.handleStreamData(data);
                };

                this.ws.onerror = (error) => {
                    console.error('🔴 接続エラー:', error);
                    this.isConnected = false;
                    this.notifyListeners(false);
                };

                this.ws.onclose = (event) => {
                    console.log(🔌 切断: ${event.code} - ${event.reason});
                    this.isConnected = false;
                    this.notifyListeners(false);
                    
                    if (!event.wasClean) {
                        this.handleReconnect();
                    }
                };

            } catch (error) {
                reject(error);
            }
        });
    }

    private handleStreamData(data: any): void {
        switch (data.type) {
            case 'content':
                this.onChunkReceived?.(data.content);
                break;
            case 'done':
                this.onStreamComplete?.(data.fullContent);
                break;
            case 'error':
                console.error('🚨 ストリームエラー:', data.message);
                this.onError?.(new Error(data.message));
                break;
            default:
                console.log('未知のメッセージタイプ:', data.type);
        }
    }

    private async handleReconnect(): Promise {
        if (this.reconnectConfig.currentRetries >= this.reconnectConfig.maxRetries) {
            console.error('❌ 再接続的最大試行回数に達しました');
            return;
        }

        const delay = this.calculateReconnectDelay();
        this.reconnectConfig.currentRetries++;
        
        console.log(⏰ ${delay}ms後に再接続 (${this.reconnectConfig.currentRetries}/${this.reconnectConfig.maxRetries}));
        
        await this.delay(delay);
        
        try {
            await this.connect();
        } catch (error) {
            this.handleReconnect();
        }
    }

    private calculateReconnectDelay(): number {
        const exponential = Math.pow(2, this.reconnectConfig.currentRetries);
        const delay = Math.min(
            this.reconnectConfig.baseInterval * exponential,
            this.reconnectConfig.maxInterval
        );
        // ジッター追加(10-20%)
        const jitter = delay * (0.1 + Math.random() * 0.1);
        return Math.floor(delay + jitter);
    }

    private delay(ms: number): Promise {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    private flushQueue(): void {
        while (this.messageQueue.length > 0) {
            const msg = this.messageQueue.shift();
            this.send(msg);
        }
    }

    send(message: any): void {
        if (this.isConnected && this.ws?.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(message));
        } else {
            console.log('📤 メッセージをキューに追加');
            this.messageQueue.push(message);
        }
    }

    async reconnect(): Promise {
        this.reconnectConfig.currentRetries = 0;
        await this.connect();
    }

    disconnect(): void {
        this.ws?.close(1000, 'Client disconnect');
        this.ws = null;
        this.isConnected = false;
    }

    onChunkReceived?: (chunk: string) => void;
    onStreamComplete?: (fullContent: string) => void;
    onError?: (error: Error) => void;

    addConnectionListener(listener: (connected: boolean) => void): void {
        this.connectionListeners.push(listener);
    }

    private notifyListeners(connected: boolean): void {
        this.connectionListeners.forEach(listener => listener(connected));
    }
}

// 使用例
async function main() {
    const client = new HolySheepStreamingClient('YOUR_HOLYSHEEP_API_KEY');
    
    client.onChunkReceived = (chunk) => {
        process.stdout.write(chunk); // リアルタイム出力
    };
    
    client.onStreamComplete = (fullContent) => {
        console.log('\n✅ 完了:', fullContent.length, '文字');
    };
    
    client.onError = (error) => {
        console.error('❌ エラー:', error.message);
    };
    
    client.addConnectionListener((connected) => {
        console.log(connected ? '🟢 接続中' : '🔴 切断中');
    });

    await client.connect();
    
    client.send({
        model: 'gpt-4.1',
        messages: [{ role: 'user', content: 'こんにちは!' }],
        stream: true
    });
}

4. 状態遷移図と設計パターン

// 接続状態列挙型
enum ConnectionState {
    DISCONNECTED = 'DISCONNECTED',
    CONNECTING = 'CONNECTING',
    CONNECTED = 'CONNECTED',
    RECONNECTING = 'RECONNECTING',
    FAILED = 'FAILED'
}

// 状態遷移マネージャー
class StateMachine {
    private state: ConnectionState = ConnectionState.DISCONNECTED;
    private stateListeners: Map<ConnectionState, Function[]> = new Map();

    getState(): ConnectionState {
        return this.state;
    }

    transition(newState: ConnectionState): void {
        const oldState = this.state;
        this.state = newState;
        
        console.log(🔄 状態遷移: ${oldState} → ${newState});
        
        const listeners = this.stateListeners.get(newState);
        if (listeners) {
            listeners.forEach(fn => fn(newState, oldState));
        }
    }

    onStateChange(state: ConnectionState, callback: Function): void {
        if (!this.stateListeners.has(state)) {
            this.stateListeners.set(state, []);
        }
        this.stateListeners.get(state)!.push(callback);
    }
}

// 完全な再接続戦略クラス
class ReconnectionStrategy {
    constructor(
        private maxAttempts: number = 10,
        private initialDelay: number = 1000,
        private maxDelay: number = 30000,
        private multiplier: number = 1.5,
        private jitter: number = 0.3
    ) {}

    getNextDelay(attempt: number): number {
        if (attempt >= this.maxAttempts) {
            return -1; // 再接続不可
        }

        // エクスポネンシャルバックオフ計算
        const exponentialDelay = this.initialDelay * Math.pow(this.multiplier, attempt);
        const cappedDelay = Math.min(exponentialDelay, this.maxDelay);
        
        // ジッター適用
        const jitterRange = cappedDelay * this.jitter;
        const randomizedDelay = cappedDelay + (Math.random() * 2 - 1) * jitterRange;
        
        return Math.floor(randomizedDelay);
    }

    shouldRetry(attempt: number): boolean {
        return attempt < this.maxAttempts;
    }

    reset(): void {
        // 戦略のリセット
    }
}

// 使用例
const strategy = new ReconnectionStrategy({
    maxAttempts: 10,
    initialDelay: 1000,
    maxDelay: 30000,
    multiplier: 2,
    jitter: 0.3
});

for (let attempt = 0; attempt < 5; attempt++) {
    const delay = strategy.getNextDelay(attempt);
    console.log(試行 ${attempt + 1}: ${delay}ms);
}
// 出力例:
// 試行 1: ~1000ms
// 試行 2: ~2000ms
// 試行 3: ~4000ms
// 試行 4: ~8000ms
// 試行 5: ~16000ms

5. 接続品質監視の実装

// 接続品質モニタリングクラス
class ConnectionQualityMonitor {
    private pingTimestamps: number[] = [];
    private readonly maxSamples = 10;
    private readonly pingInterval = 5000;
    private readonly acceptableLatency = 500; // ms
    private timer: number | null = null;
    private qualityCallback: ((quality: 'excellent' | 'good' | 'poor' | 'critical') => void) | null = null;

    start(ws: WebSocket): void {
        this.timer = window.setInterval(() => {
            if (ws.readyState === WebSocket.OPEN) {
                const pingId = Date.now();
                ws.send(JSON.stringify({ type: 'ping', timestamp: pingId }));
            }
        }, this.pingInterval);
    }

    recordPong(timestamp: number): void {
        const latency = Date.now() - timestamp;
        this.pingTimestamps.push(latency);
        
        if (this.pingTimestamps.length > this.maxSamples) {
            this.pingTimestamps.shift();
        }

        this.evaluateQuality();
    }

    private evaluateQuality(): 'excellent' | 'good' | 'poor' | 'critical' {
        if (this.pingTimestamps.length === 0) return 'excellent';
        
        const avgLatency = this.pingTimestamps.reduce((a, b) => a + b) / this.pingTimestamps.length;
        const quality = this.calculateQuality(avgLatency);
        
        console.log(📊 接続品質: ${quality} (平均レイテンシ: ${avgLatency.toFixed(2)}ms));
        
        this.qualityCallback?.(quality);
        
        return quality;
    }

    private calculateQuality(latency: number): 'excellent' | 'good' | 'poor' | 'critical' {
        // HolySheepの<50msレイテンシ環境を基準とした評価
        if (latency < 50) return 'excellent';
        if (latency < 100) return 'good';
        if (latency < 500) return 'poor';
        return 'critical';
    }

    setQualityCallback(callback: (quality: 'excellent' | 'good' | 'poor' | 'critical') => void): void {
        this.qualityCallback = callback;
    }

    getAverageLatency(): number {
        if (this.pingTimestamps.length === 0) return 0;
        return this.pingTimestamps.reduce((a, b) => a + b) / this.pingTimestamps.length;
    }

    stop(): void {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
}

よくあるエラーと対処法

エラー1: WebSocket接続が突然切断される(Code: 1006)

// ❌ 問題: ブラウザのタブが非アクティブになった際に接続が切断される
// 🔧 解決: visibilitychangeイベントで再接続をトリガー

document.addEventListener('visibilitychange', async () => {
    if (document.visibilityState === 'visible' && !client.isConnected()) {
        console.log('ページを離れていました。再接続します...');
        await client.reconnect();
    }
});

// 追加: ネットワーク状態変化の監視
window.addEventListener('online', () => {
    console.log('🌐 ネットワーク回復を検知');
    client.reconnect();
});

window.addEventListener('offline', () => {
    console.log('📴 ネットワーク切断を検知');
    client.disconnect();
});

エラー2: 再接続が永久にループする

// ❌ 問題: サーバー側でアクセスがブロックされている場合、無限再接続ループが発生
// 🔧 解決: 最大試行回数を設定し、問題が解決しない場合はユーザーに通知

class SmartReconnectManager {
    private retryCount = 0;
    private maxRetries = 5;
    private readonly retryKey = 'last_retry_timestamp';
    
    async reconnect(): Promise<boolean> {
        // 同一セッション内での短時間再試行を制限
        const lastRetry = localStorage.getItem(this.retryKey);
        const now = Date.now();
        
        if (lastRetry && now - parseInt(lastRetry) < 60000) {
            console.warn('⚠️ 短時間内の再試行を制限しています');
            this.notifyUser();
            return false;
        }
        
        if (this.retryCount >= this.maxRetries) {
            console.error('❌ 最大再試行回数に達しました');
            this.notifyUser();
            return false;
        }
        
        localStorage.setItem(this.retryKey, now.toString());
        this.retryCount++;
        
        return this.attemptReconnect();
    }
    
    private notifyUser(): void {
        // UIでユーザーに通知
        const notification = document.getElementById('connection-status');
        if (notification) {
            notification.textContent = '接続に問題があります。ページを更新してください。';
            notification.className = 'error';
        }
    }
}

エラー3: メッセージの順序保証がない

// ❌ 問題: 再接続後、送信したメッセージが失われる・順序が狂う
// 🔧 解決: メッセージキューとACKシステムの実装

class ReliableMessageQueue {
    private queue: Map<string, { message: any, timestamp: number, retries: number }> = new Map();
    private pendingAck: Set<string> = new Set();
    private readonly maxRetries = 3;
    private readonly ackTimeout = 5000;
    
    send(messageId: string, message: any): void {
        if (this.pendingAck.has(messageId)) {
            console.warn('⌛ メッセージはACK待ちです:', messageId);
            return;
        }
        
        this.queue.set(messageId, {
            message,
            timestamp: Date.now(),
            retries: 0
        });
        
        this.transmit(messageId, message);
    }
    
    private transmit(messageId: string, message: any): void {
        ws.send(JSON.stringify({ ...message, messageId }));
        
        // ACK待機タイマー
        setTimeout(() => {
            if (this.pendingAck.has(messageId)) {
                const item = this.queue.get(messageId);
                if (item && item.retries < this.maxRetries) {
                    item.retries++;
                    console.log(🔄 メッセージ再送: ${messageId} (${item.retries}回目));
                    this.transmit(messageId, item.message);
                } else {
                    console.error(❌ メッセージ送信失敗: ${messageId});
                    this.queue.delete(messageId);
                }
                this.pendingAck.delete(messageId);
            }
        }, this.ackTimeout);
    }
    
    handleAck(messageId: string): void {
        this.pendingAck.add(messageId);
        this.queue.delete(messageId);
        setTimeout(() => this.pendingAck.delete(messageId), 1000);
    }
}

まとめ

WebSocketの再接続メカニズムを実装する上で、本稿で説明した以下のポイントが重要です:

HolySheep AIでは、¥1=$1のレート(公式比85%節約)に加えて、WeChat Pay/Alipay対応、<50msの超低レイテンシ環境を提供しており、大量リクエストを安定して処理できます。

私は以前他社APIで切断問題が频発し、ユーザー体験が大きく損なわれる経験をしました。HolySheepに移行後は、堅牢な接続管理と低レイテンシの組み合わせで、同様の問題を大幅に減らせています。

リアルタイム通信の安定性を求めているなら、HolySheep AIの環境をぜひ試してみてください。

👉 HolySheep AI に登録して無料クレジットを獲得