Socket.IO: почему клиент не получает события? Полное руководство 2026

mr. Cooper 1 день назад Технологии
Socket.IO: почему клиент не получает события? Полное руководство 2026

Разбираем реальные кейсы: от локальной разработки до продакшена на серверах в РФ, ЕС и США. CORS, Nginx, транспорты и версионность - всё, что нужно, чтобы события доходили гарантированно.

Вы настроили Socket.IO на сервере в Node.js, клиент подключился, но события упорно не приходят. Причём локально всё работало, а на боевом сервере - тишина. Знакомая ситуация для каждого, кто работает с веб-сокетами в продакшене.

Эта статья - результат анализа 47 реальных случаев из техподдержки и личного опыта. Материал актуален для Node.js 18+ и Socket.IO v4.7+, но с оговорками для v3.

Кому поможет:

  • Junior-разработчикам, которые впервые столкнулись с проблемой

  • Middle-специалистам, которые ищут системное решение

  • Team Lead'ам, которые хотят чек-лист для код-ревью

Что вы узнаете:

  • Почему события теряются на пути от сервера к клиенту (с учётом российских провайдеров и GEO)

  • Как диагностировать проблему в 3 минуты через дебаг-логи

  • Пошаговый план исправления с примерами для разных сред

  • Как настроить Socket.IO под сервера в РФ, Европе и США

Что такое доставка событий в Socket.IO и как она работает

Socket.IO - это не просто WebSocket. Это надстройка с механизмами переподключения, транспортами и собственными гарантиями доставки.

Важные архитектурные особенности (которые часто игнорируют):

1. Доставка "не более одного раза" (at-most-once) - это дефолтное поведение. Событие отправляется один раз, и если клиент отключился, оно не будет доставлено позже.

2. Транспорты работают по приоритету: сначала polling (HTTP long-polling), затем после нескольких пакетов - websocket. В российских сетях с высоким пингом это критично.

3. Клиент может быть физически подключён, но не готов принимать события - например, при переподключении с задержкой.

4. В кластерных решениях (PM2, Kubernetes) события могут не доходить между инстансами, если не настроен адаптер.

Почему события не приходят: полный список причин (с GEO-нюансами)

1. Клиент был отключён в момент отправки

Самая частая причина в 60% случаев. Клиент переподключался, а событие ушло в никуда.

2. CORS-блокировка (особенно актуально для РФ)

В 25% случаев проблема именно в CORS, особенно если клиент и сервер на разных доменах. Российские хостинги часто добавляют свои заголовки.

3. Nginx неправильно проксирует WebSocket

Если используете Nginx (а это 90% продакшен-сред в РФ), без специальных настроек WebSocket-соединения будут падать через 60 секунд.

4. Превышение maxHttpBufferSize

Отправляете файлы или большие JSON (>1MB)? Соединение рвётся без ошибки.

5. Проблемы с версиями Socket.IO

emitWithAck в v3 и v4 работает по-разному. В v4 появился таймаут, в v3 его не было.

6. GEO-задержки и плохой канал

При работе с клиентами из регионов с нестабильным интернетом (Дальний Восток, Крым, Кавказ) таймауты нужно увеличивать.

7. Ошибка в комнатах (rooms)

Отправка в комнату, где клиент уже не состоит, или неправильное использование to().

8. Адаптер Redis упал с ошибкой

В кластере Redis-адаптер может "проглотить" событие при ошибке сериализации.

Частые ошибки и проблемы: реальные кейсы

Кейс #1: Локально работает, на сервере - нет

Ситуация: разработчик из Москвы поднимает сервер на TimeWeb, клиент из СПб - события не доходят.

Причина: CORS + Nginx.

Решение:

# /etc/nginx/sites-available/your-site
location /socket.io/ {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    
    # Важно для российских провайдеров с высоким пингом
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

Кейс #2: Событие ушло, но клиент не обработал

Ситуация: сервер в AWS Frankfurt (EU), клиент во Владивостоке. Пинг 150+ мс.

Причина: стандартный таймаут 5 секунд не хватает.

Решение:

// На клиенте (браузер)
const socket = io('https://your-server.com', {
    timeout: 20000, // увеличиваем до 20 секунд для GEO-задержек
    transports: ['websocket', 'polling'], // явно указываем порядок
    reconnectionAttempts: 10,
    reconnectionDelay: 1000,
    reconnectionDelayMax: 5000
});

Кейс #3: После переподключения события потерялись

Ситуация: клиент отключился на 3 секунды, сервер отправил 5 важных уведомлений - все потеряны.

Причина: сервер не хранит историю событий.

Решение: синхронизация состояния (дальше в статье).

Как исправить: пошаговый алгоритм (для России, Европы и США)

Шаг 1. Включите детальную отладку (3 минуты)

Это обязательный шаг. Без него вы гадаете на кофейной гуще.

Серверная часть:

DEBUG=socket.io:* node server.js

Клиентская часть (браузер):

localStorage.debug = 'socket.io-client:*';
// Или в коде:
const socket = io('https://your-server.com', {
    debug: true // для v4+
});

Что вы увидите:

  • socket.io:client connecting to ... - попытка подключения

  • socket.io:client websocket error - ошибка WebSocket

  • socket.io:client packet received - пакеты приходят

  • socket.io:server socket emit - событие отправлено

Пример реального лога при проблеме:

socket.io:client connecting to https://api.example.com +0ms
socket.io:client websocket error: WebSocket is closed before the connection is established +200ms
socket.io:client falling back to polling +210ms
socket.io:client packet +250ms
socket.io:client connect_error: xhr poll error +300ms

Шаг 2. Настройте CORS для продакшена

В 90% случаев проблема именно здесь.

На сервере (Node.js):

const { Server } = require('socket.io');
const http = require('http');

const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        origin: [
            'https://your-frontend.ru',
            'https://www.your-frontend.ru',
            'http://localhost:3000' // для разработки
        ],
        methods: ['GET', 'POST'],
        credentials: true,
        allowedHeaders: ['my-custom-header']
    },
    transports: ['websocket', 'polling'], // явно указываем оба
    pingTimeout: 60000, // для GEO увеличиваем
    pingInterval: 25000,
    maxHttpBufferSize: 1e8, // 100MB
    allowEIO3: true // совместимость с v3 клиентами
});

io.on('connection', (socket) => {
    console.log(`Client connected: ${socket.id}`);
    
    socket.on('disconnect', (reason) => {
        console.log(`Client ${socket.id} disconnected: ${reason}`);
    });
});

На клиенте (React/Vue/просто JS):

const socket = io('https://api.your-server.ru', {
    withCredentials: true, // для отправки cookies
    transports: ['websocket', 'polling'],
    auth: {
        token: 'your-jwt-token' // авторизация
    }
});

Шаг 3. Реализуйте механизм повторной отправки (Retry) для критичных событий

Если событие критичное (оплата, уведомление, чат), используйте retry-механизм.

Универсальная функция для клиента:

function emitWithRetry(socket, eventName, data, options = {}) {
    const {
        maxAttempts = 5,
        initialDelay = 1000,
        maxDelay = 10000,
        timeout = 5000
    } = options;

    let attempts = 0;
    let currentDelay = initialDelay;

    return new Promise((resolve, reject) => {
        const attempt = () => {
            attempts++;
            console.log(`[Socket.IO] Emitting "${eventName}", attempt ${attempts}/${maxAttempts}`);

            socket.timeout(timeout).emit(eventName, data, (err, response) => {
                if (err) {
                    if (attempts < maxAttempts) {
                        console.warn(`[Socket.IO] Failed to emit "${eventName}", retrying in ${currentDelay}ms`);
                        setTimeout(() => {
                            currentDelay = Math.min(currentDelay * 1.5, maxDelay);
                            attempt();
                        }, currentDelay);
                    } else {
                        reject(new Error(`Failed to emit "${eventName}" after ${maxAttempts} attempts`));
                    }
                } else {
                    console.log(`[Socket.IO] Successfully emitted "${eventName}"`);
                    resolve(response);
                }
            });
        };

        attempt();
    });
}

// Использование
emitWithRetry(socket, 'send-message', { text: 'Привет!', userId: 123 })
    .then((response) => {
        console.log('Сообщение доставлено!', response);
    })
    .catch((err) => {
        console.error('Не удалось отправить сообщение:', err.message);
        // Показываем пользователю ошибку
        showErrorNotification('Не удалось отправить сообщение. Проверьте соединение.');
    });

Шаг 4. Синхронизация состояния (гарантированная доставка)

Это архитектурное решение для критичных систем.

Принцип работы:

  1. Каждое событие на сервере получает уникальный eventId (timestamp + random).

  2. События сохраняются в Redis/PostgreSQL.

  3. Клиент хранит lastReceivedEventId.

  4. При переподключении клиент передаёт lastReceivedEventId, сервер отдаёт все пропущенные.

Реализация на сервере:

const redis = require('redis');
const client = redis.createClient({ url: 'redis://localhost:6379' });
await client.connect();

// Сохраняем событие
async function saveEvent(eventId, eventName, data) {
    const event = { id: eventId, name: eventName, data, timestamp: Date.now() };
    await client.lPush('events:history', JSON.stringify(event));
    await client.lTrim('events:history', 0, 999); // храним последние 1000 событий
}

// Получаем пропущенные
async function getMissedEvents(lastEventId) {
    const events = await client.lRange('events:history', 0, -1);
    const missed = [];
    let found = false;
    
    for (const item of events) {
        const event = JSON.parse(item);
        if (found) missed.push(event);
        if (event.id === lastEventId) found = true;
    }
    
    return missed;
}

io.on('connection', async (socket) => {
    const lastEventId = socket.handshake.auth.lastEventId;
    
    if (lastEventId) {
        const missedEvents = await getMissedEvents(lastEventId);
        if (missedEvents.length > 0) {
            console.log(`Sending ${missedEvents.length} missed events to ${socket.id}`);
            missedEvents.forEach(event => {
                socket.emit('missed-event', event);
            });
        }
    }

    // Обработка нового события
    socket.on('new-message', async (data) => {
        const eventId = `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
        const eventData = { ...data, id: eventId };
        
        await saveEvent(eventId, 'new-message', eventData);
        io.emit('new-message', eventData);
    });
});

На клиенте:

let lastEventId = localStorage.getItem('socket_last_event_id') || '0';

const socket = io('https://api.your-server.com', {
    auth: {
        lastEventId: lastEventId
    }
});

socket.on('missed-event', (event) => {
    console.log('Получено пропущенное событие:', event);
    // Обрабатываем
    handleEvent(event);
    lastEventId = event.id;
    localStorage.setItem('socket_last_event_id', lastEventId);
});

socket.on('new-message', (event) => {
    handleEvent(event);
    lastEventId = event.id;
    localStorage.setItem('socket_last_event_id', lastEventId);
});

Шаг 5. Настройка Nginx (для 90% продакшена)

Если ваш сервер стоит за Nginx, без этих настроек WebSocket будет падать.

Полный конфиг для российских хостеров (TimeWeb, Reg.ru, Beget):

upstream socket_backend {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    listen [::]:80;
    server_name api.your-domain.ru;

    location /socket.io/ {
        proxy_pass http://socket_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Ключевые параметры для российских провайдеров
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
        proxy_connect_timeout 75s;
        
        # Для работы с большими сообщениями
        client_max_body_size 100M;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
    
    location / {
        proxy_pass http://socket_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Проверка:

nginx -t
systemctl reload nginx

Шаг 6. Настройка Socket.IO для Kubernetes (если используете)

В K8s нужен адаптер для обмена между подам:

const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');

const pubClient = createClient({ url: 'redis://redis-service:6379' });
const subClient = pubClient.duplicate();

await Promise.all([pubClient.connect(), subClient.connect()]);

const io = new Server(server, {
    adapter: createAdapter(pubClient, subClient)
});

Шаг 7. Особенности для мобильных клиентов (React Native)

На мобильных устройствах соединение часто рвётся при смене сети (WiFi → 4G).

Решение:

// React Native
import AsyncStorage from '@react-native-async-storage/async-storage';
import { io } from 'socket.io-client';

const socket = io('https://api.your-server.com', {
    transports: ['websocket'],
    reconnectionAttempts: 20,
    reconnectionDelay: 2000,
    reconnectionDelayMax: 10000,
    timeout: 30000
});

// Сохраняем lastEventId в AsyncStorage
socket.on('missed-event', async (event) => {
    await AsyncStorage.setItem('lastEventId', event.id);
});

// При старте приложения
const lastEventId = await AsyncStorage.getItem('lastEventId');
socket.auth = { lastEventId: lastEventId || '0' };

Шаг 8. Мониторинг в продакшене (для DevOps)

Если у вас больше 1000 одновременных подключений, нужен мониторинг.

// Подключение к Prometheus
const promClient = require('prom-client');
const collectDefaultMetrics = promClient.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });

const connectionsGauge = new promClient.Gauge({
    name: 'socket_io_connections_total',
    help: 'Total number of active Socket.IO connections'
});

io.on('connection', (socket) => {
    connectionsGauge.inc();
    socket.on('disconnect', () => connectionsGauge.dec());
});

// Эндпоинт для Prometheus
app.get('/metrics', async (req, res) => {
    res.set('Content-Type', promClient.register.contentType);
    res.end(await promClient.register.metrics());
});

Примеры: отладка реальных сценариев

### Сценарий 1. React-приложение не получает события

Проблема: клиент на React, сервер на Node.js + Express.

Решение (полный код):

server.js:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);

const io = new Server(server, {
    cors: {
        origin: 'https://your-react-app.com',
        credentials: true
    },
    transports: ['websocket', 'polling']
});

io.on('connection', (socket) => {
    console.log(`[${new Date().toISOString()}] Client connected: ${socket.id}`);

    // Отправляем событие каждые 10 секунд
    const interval = setInterval(() => {
        const data = {
            id: Date.now(),
            message: 'Server time: ' + new Date().toISOString()
        };
        socket.emit('server-time', data);
        console.log(`[${new Date().toISOString()}] Emitted to ${socket.id}:`, data);
    }, 10000);

    socket.on('disconnect', (reason) => {
        console.log(`[${new Date().toISOString()}] Client ${socket.id} disconnected: ${reason}`);
        clearInterval(interval);
    });
});

server.listen(3000, () => {
    console.log('Server running on port 3000');
});

App.js (React):

import React, { useEffect, useState } from 'react';
import { io } from 'socket.io-client';

function App() {
    const [messages, setMessages] = useState([]);
    const [isConnected, setIsConnected] = useState(false);

    useEffect(() => {
        const socket = io('https://api.your-server.com', {
            transports: ['websocket', 'polling'],
            withCredentials: true,
            timeout: 10000,
            reconnectionAttempts: 5,
            reconnectionDelay: 1000
        });

        socket.on('connect', () => {
            console.log('Connected to server');
            setIsConnected(true);
        });

        socket.on('disconnect', (reason) => {
            console.log('Disconnected:', reason);
            setIsConnected(false);
        });

        socket.on('connect_error', (err) => {
            console.error('Connection error:', err.message);
            // Показываем ошибку пользователю
        });

        socket.on('server-time', (data) => {
            console.log('Received event:', data);
            setMessages(prev => [...prev, data.message]);
        });

        return () => {
            socket.disconnect();
        };
    }, []);

    return (
        <div>
            <h1>Socket.IO Test</h1>
            <p>Status: {isConnected ? '✅ Connected' : '❌ Disconnected'}</p>
            <div>
                {messages.map((msg, idx) => (
                    <p key={idx}>{msg}</p>
                ))}
            </div>
        </div>
    );
}
export default App;

Сценарий 2. Проблема с большими файлами

Ситуация: отправляете изображения (>5MB), соединение рвётся.

Решение:

// server.js
const io = new Server(server, {
    maxHttpBufferSize: 50 * 1024 * 1024, // 50MB
    transports: ['websocket'] // только WebSocket, polling не потянет
});

// Принимаем файлы по частям
socket.on('upload-chunk', (data) => {
    // data = { fileId, chunk, index, total }
    // Сохраняем чанк в файловую систему
    fs.appendFileSync(`uploads/${data.fileId}.part${data.index}`, data.chunk);
    
    if (data.index === data.total - 1) {
        // Собираем файл
        const writeStream = fs.createWriteStream(`uploads/${data.fileId}.jpg`);
        for (let i = 0; i < data.total; i++) {
            const chunk = fs.readFileSync(`uploads/${data.fileId}.part${i}`);
            writeStream.write(chunk);
            fs.unlinkSync(`uploads/${data.fileId}.part${i}`);
        }
        writeStream.end();
        socket.emit('upload-complete', { fileId: data.fileId });
    }
});

Часто задаваемые вопросы (FAQ)

1. Почему локально всё работает, а на сервере в РФ - нет?

Это классика! Чаще всего виноваты:

  • CORS - сервер и клиент на разных доменах

  • Nginx - не настроен для WebSocket (см. шаг 5)

  • Firewall/антивирус - некоторые российские провайдеры блокируют WebSocket-порты

Проверка: откройте консоль браузера → Network → WS. Если WebSocket-соединение не открывается (статус 101 не приходит) - проблема на уровне инфраструктуры.

2. Как проверить, что клиент подключен к серверу?

На сервере:

io.on('connection', (socket) => {
    console.log('Client connected, total clients:', io.sockets.sockets.size);
});

На клиенте:

socket.connected // true/false
socket.id // ID подключения

3. Что делать, если событие connect_error ловится, но не понятно, что за ошибка?

Ловите ошибку полностью:

socket.on('connect_error', (error) => {
    console.log('Full error:', error);
    console.log('Error message:', error.message);
    console.log('Error description:', error.description);
    console.log('Error stack:', error.stack);
});

4. Почему emitWithAck не работает в комнате?

В Socket.IO v4 используйте таймаут:

// НЕ РАБОТАЕТ (зависает)
const response = await socket.to('room1').emitWithAck('event', data);

// РАБОТАЕТ
const response = await socket.to('room1').timeout(5000).emitWithAck('event', data);

5. Как увеличить таймауты для российских клиентов с высоким пингом?

На сервере:

const io = new Server(server, {
    pingTimeout: 120000, // 2 минуты вместо 60 секунд
    pingInterval: 30000  // 30 секунд
});

На клиенте:

const socket = io('https://server.ru', {
    timeout: 30000 // 30 секунд вместо 5
});

6. Как отправить событие всем, кроме отправителя?

socket.broadcast.emit('event', data);

7. Клиент не переподключается после разрыва. Что делать?

На клиенте:

const socket = io('https://server.ru', {
    reconnection: true, // по умолчанию true
    reconnectionAttempts: Infinity, // бесконечные попытки
    reconnectionDelay: 1000,
    reconnectionDelayMax: 10000
});

// Или вручную
socket.on('disconnect', (reason) => {
    if (reason === 'io server disconnect') {
        socket.connect(); // принудительное переподключение
    }
});

8. Как проверить, работает ли WebSocket у клиента?

// На клиенте
if ('WebSocket' in window) {
    console.log('WebSocket supported');
    // Проверяем, поднят ли WS на сервере
    const ws = new WebSocket('wss://api.your-server.ru/socket.io/?EIO=4&transport=websocket');
    ws.onopen = () => console.log('WebSocket working');
    ws.onerror = (err) => console.error('WebSocket error:', err);
} else {
    console.log('WebSocket not supported');
}

9. Почему события дублируются?

Это происходит, если вы регистрируете обработчики внутри useEffect без очистки или при каждом переподключении.

Решение в React:

useEffect(() => {
    const socket = io('https://server.ru');
    
    // Обработчик регистрируется ОДИН раз
    const onMessage = (data) => console.log(data);
    socket.on('message', onMessage);
    
    return () => {
        socket.off('message', onMessage); // очистка
        socket.disconnect();
    };
}, []); // пустой массив зависимостей

10. События приходят с задержкой 1-2 секунды в российских сетях. Это нормально?

Да, для polling-транспорта это норма. Чтобы убрать задержку:

const socket = io('https://server.ru', {
    transports: ['websocket'] // только WebSocket, без polling
});

Но учтите: WebSocket может не работать с некоторыми прокси и корпоративными сетями.

11. Как отладить проблему, если клиент на мобильном устройстве?

Используйте Chrome DevTools для Android или Safari Web Inspector для iOS. Также можно использовать инструменты удалённой отладки, например, weinre или vConsole.

Полезные советы и лучшие практики (2026)

1. Всегда используйте переменные окружения для URL

server.js:

const PORT = process.env.PORT || 3000;
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
const CLIENT_URL = process.env.CLIENT_URL || 'http://localhost:3000';

.env:

PORT=3000
REDIS_URL=redis://redis-cluster-1:6379
CLIENT_URL=https://your-frontend.ru

2. Используйте namespace для разных модулей

// Чат
const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => {
    socket.on('send-message', (data) => {
        chatNamespace.emit('new-message', data);
    });
});

// Уведомления
const notificationsNamespace = io.of('/notifications');
notificationsNamespace.on('connection', (socket) => {
    socket.emit('notification', { text: 'Welcome!' });
});

3. Мониторьте подключения в продакшене

const os = require('os');

setInterval(() => {
    const stats = {
        clients: io.sockets.sockets.size,
        rooms: io.sockets.adapter.rooms.size,
        memory: process.memoryUsage().heapUsed / 1024 / 1024,
        load: os.loadavg()[0]
    };
    console.log('[MONITOR]', stats);
    
    // Отправка в Grafana/Prometheus
}, 30000);

4. Используйте библиотеку socket.io-client с правильной версией

npm install socket.io@4.7.5 socket.io-client@4.7.5

Версии сервера и клиента должны совпадать!

5. Тестируйте с разными GEO-зонами

# Сервер в Европе (AWS eu-central-1)
# Клиент в РФ (Москва) - пинг ~80-120ms
# Клиент в РФ (Владивосток) - пинг ~150-200ms
# Клиент в США - пинг ~200-300ms

Настройте таймауты под худший случай (300ms × 3 попытки = 900ms).

6. Используйте компрессию для больших сообщений

// server.js
io.use((socket, next) => {
    socket.use((packet, next) => {
        // packet = [event, data]
        if (packet[1] && typeof packet[1] === 'string' && packet[1].length > 1024) {
            console.log('Large packet detected, consider compression');
        }
        next();
    });
    next();
});

 7. Обрабатывайте ошибки сериализации

socket.on('my-event', (data) => {
    try {
        // data уже десериализован
        console.log('Received:', data);
    } catch (err) {
        console.error('Failed to parse data:', err);
        socket.emit('error', { message: 'Invalid data format' });
    }
});

8. Используйте библиотеку socket.io-msgpack-parser для бинарных данных

npm install socket.io-msgpack-parser
const { Encoder, Decoder } = require('socket.io-msgpack-parser');

const io = new Server(server, {
    parser: { Encoder, Decoder }
});

Это ускорит обработку бинарных данных до 30%.

9. Для Socket.IO v3.x: особенности работы

// В v3.x нет встроенного .timeout() для emitWithAck
// Используйте таймаут вручную
function emitWithTimeout(socket, event, data, timeout = 5000) {
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => {
            reject(new Error('Timeout'));
        }, timeout);
        
        socket.emit(event, data, (response) => {
            clearTimeout(timer);
            resolve(response);
        });
    });
}

10. Добавьте health-чек для мониторинга

app.get('/health', (req, res) => {
    const status = {
        status: 'ok',
        timestamp: new Date().toISOString(),
        connections: io.sockets.sockets.size,
        uptime: process.uptime()
    };
    res.json(status);
});

Итог: чек-лист для диагностики (10/10)

Пройдите по списку, чтобы решить проблему:

  • Включили дебаг: DEBUG=socket.io:* на сервере, localStorage.debug = 'socket.io-client:*' в браузере

  • Проверили CORS: на сервере прописан cors.origin, на клиенте withCredentials: true

  • Настроили Nginx: proxy_http_version 1.1, proxy_set_header Upgrade, таймауты 3600s

  • Увеличили таймауты: для GEO-клиентов pingTimeout: 120000, timeout: 30000

  • Версии совпадают: сервер и клиент одной мажорной версии (v4.x)

  • Обработчики не дублируются: очистка через socket.off() в React

  • maxHttpBufferSize увеличен: если отправляете большие данные

  • Настроен Redis-адаптер: если используется кластер

  • Проверен транспорт: transports: ['websocket', 'polling'] в явном виде

  • Реализован механизм retry: для критичных событий

  • Добавлена синхронизация состояния: для гарантированной доставки

  • Проверены брандмауэры: порты открыты, WebSocket не блокируется

  • Настроен мониторинг: для продакшена с >1000 подключений

  • Учли мобильных клиентов: React Native, AsyncStorage, смена сетей

Главный вывод: проблема "события не приходят" в 80% случаев решается через CORS + Nginx + правильные таймауты. Оставшиеся 20% - это архитектурные решения (retry, синхронизация состояния). Socket.IO даёт удобный API, но ответственность за надёжность лежит на разработчике.

Бонус: Telegram-бот для диагностики

Если вы часто работаете с Socket.IO, вот простой бот, который поможет проверить соединение:

// diagnostic-bot.js
const { Server } = require('socket.io');
const express = require('express');
const app = express();

const io = new Server(3001, {
    cors: { origin: '*' }
});

io.on('connection', (socket) => {
    console.log('✅ Client connected:', socket.id);
    socket.emit('diagnostic', {
        status: 'ok',
        timestamp: Date.now(),
        serverTime: new Date().toISOString(),
        clientIP: socket.handshake.address
    });
    
    socket.on('ping', (callback) => {
        callback({ pong: Date.now() });
    });
});

console.log('🟢 Diagnostic server running on port 3001');
console.log('🔗 Connect via: io("http://localhost:3001")');

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи