Socket.IO: основы
Что такое Socket.IO
Socket.IO — библиотека для real-time с fallback на long polling.
Что даёт:
- WebSocket — когда возможно.
- Long polling — когда нет.
- Автопереподключение.
- Ack / emit с ответом.
- Комнаты.
- Namespaces.
- Middleware.
Установка
Сервер:
npm install socket.io
Клиент (браузер):
npm install socket.io-client
Сервер — базовый
import { Server } from 'socket.io';
const io = new Server(8080, {
cors: {
origin: ['http://localhost:3000'],
credentials: true,
},
});
io.on('connection', (socket) => {
console.log('Подключился:', socket.id);
socket.on('disconnect', () => {
console.log('Отключился:', socket.id);
});
});
console.log('Socket.IO сервер на порту 8080');
Разбор:
new Server(port, options)— сервер.io.on('connection', fn)— новый клиент.socket— конкретный клиент.socket.id— уникальный ID.
Клиент — базовый
import { io } from 'socket.io-client';
const socket = io('http://localhost:8080');
socket.on('connect', () => {
console.log('Подключено:', socket.id);
});
socket.on('disconnect', () => {
console.log('Отключено');
});
Socket.IO сам установит WebSocket или fallback.
Events
Socket.IO — event-driven. Основные:
Сервер:
io.on('connection', (socket) => {
socket.on('custom:event', (data) => {
// получить от клиента
});
});
Клиент:
socket.on('custom:event', (data) => {
// получить от сервера
});
Emit:
socket.emit('event', data);
Сервер → клиент
Одному клиенту:
io.on('connection', (socket) => {
socket.emit('welcome', {
text: 'Привет!',
});
});
Или с задержкой:
setTimeout(() => {
socket.emit('notification', 'Новое сообщение');
}, 1000);
Клиент → сервер
// Клиент
socket.emit('chat:message', {
text: 'Привет всем!',
user: 'Иван',
});
// Сервер
io.on('connection', (socket) => {
socket.on('chat:message', (data) => {
console.log('Получено:', data.text);
});
});
Broadcast всем
io.emit — всем, включая отправителя:
io.emit('chat:message', {
text: 'Всем привет',
});
socket.broadcast.emit — всем, кроме отправителя:
socket.broadcast.emit('user:joined', {
user: socket.id,
});
socket.emit — только отправителю:
socket.emit('welcome', 'Привет лично тебе');
Ack — ответ на emit
Клиент:
socket.emit('create:post', { title: 'Hello' }, (response) => {
if (response.error) {
console.error('Ошибка:', response.error);
} else {
console.log('Создан пост:', response.id);
}
});
Сервер:
socket.on('create:post', async (data, callback) => {
try {
const post = await Post.create(data);
callback({ id: post.id });
} catch (e) {
callback({ error: e.message });
}
});
Что даёт: клиент ждёт ответ от сервера, как в HTTP.
Promise-обёртка для emit
Проблема: callback не всегда удобен.
Решение:
function emitAsync(socket, event, data) {
return new Promise((resolve) => {
socket.emit(event, data, resolve);
});
}
// Использование
const response = await emitAsync(socket, 'create:post', { title: 'X' });
Namespaces
Разные «каналы» на одном сервере:
const chatNsp = io.of('/chat');
const adminNsp = io.of('/admin');
chatNsp.on('connection', (socket) => {
socket.emit('welcome', 'Чат');
});
adminNsp.on('connection', (socket) => {
socket.emit('welcome', 'Админка');
});
Клиент:
const chat = io('http://localhost:8080/chat');
const admin = io('http://localhost:8080/admin');
Что даёт:
- Разные middleware.
- Разные состояния.
- Разные комнаты.
Комнаты
socket.join('room'):
io.on('connection', (socket) => {
socket.join('room:1');
});
Отправить в комнату:
io.to('room:1').emit('message', 'Всем в комнате 1');
Выйти:
socket.leave('room:1');
Список комнат:
socket.rooms; // Set
⚠️ каждый сокет автоматически в комнате своего socket.id.
Middleware
Авторизация:
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Нет токена'));
}
try {
const payload = jwt.verify(token, JWT_SECRET);
socket.data.user = payload;
next();
} catch (e) {
next(new Error('Неверный токен'));
}
});
Доступ к юзеру:
io.on('connection', (socket) => {
console.log('Подключился:', socket.data.user.id);
});
CORS
const io = new Server(8080, {
cors: {
origin: ['http://localhost:3000', 'https://app.example.com'],
credentials: true,
},
});
credentials: true — если cookies.
Транспорты
Только WebSocket:
const io = new Server(8080, {
transports: ['websocket'],
});
По умолчанию: ['polling', 'websocket'] — сначала polling, потом upgrade.
Проверка транспорта
На клиенте:
console.log(socket.io.engine.transport.name);
// 'websocket' или 'polling'
// При upgrade
socket.io.engine.on('upgrade', (transport) => {
console.log('Апгрейд на', transport.name);
});
Практика: чат
Сервер:
import { Server } from 'socket.io';
const io = new Server(8080, {
cors: { origin: 'http://localhost:3000' },
});
io.on('connection', (socket) => {
console.log('Подключился:', socket.id);
socket.on('chat:join', ({ name }) => {
socket.data.name = name;
io.emit('chat:system', `${name} вошёл в чат`);
});
socket.on('chat:message', ({ text }, callback) => {
if (!text?.trim()) {
return callback({ error: 'Пустое сообщение' });
}
io.emit('chat:message', {
user: socket.data.name,
text,
time: Date.now(),
});
callback({ ok: true });
});
socket.on('disconnect', () => {
if (socket.data.name) {
io.emit('chat:system', `${socket.data.name} вышел`);
}
});
});
Клиент:
import { io } from 'socket.io-client';
const socket = io('http://localhost:8080');
socket.on('connect', () => {
socket.emit('chat:join', { name: 'Иван' });
});
socket.on('chat:message', ({ user, text }) => {
console.log(`${user}: ${text}`);
});
socket.on('chat:system', (msg) => {
console.log('*** ' + msg);
});
socket.emit('chat:message', { text: 'Привет!' }, (response) => {
if (response.error) console.error(response.error);
});
Частые ошибки
1. Забыли CORS:
Access to XMLHttpRequest blocked by CORS policy
Решение: cors: { origin: [...] }.
2. Использовать socket.io-client с чистым WebSocket:
Socket.IO клиент не работает с ws-сервером
3. io.emit вместо socket.emit:
io.emit('welcome'); // все получат
// Правильно:
// socket.emit('welcome'); — только один
4. Забыли credentials:
// Клиент не отправит cookies
// Правильно:
cors: { origin: '...', credentials: true }
5. Не обрабатывать disconnect:
Клиент отключился, а сервер всё ещё в комнатах
Итог
- Socket.IO — real-time с fallback.
new Server(port, opts)— сервер.io.on('connection', fn)— клиент.socket.emit,socket.on— события.io.emit— всем,socket.broadcast.emit— кроме отправителя.- Ack — callback в emit.
- Namespaces — каналы.
- Комнаты —
join,leave,to. - Middleware — авторизация.
Дальше — Socket.IO: комнаты и события.