WebSocket API в браузере
WebSocket API в браузере
Теория WebSocket'ов понятна. Но как это выглядит в коде? Разберём WebSocket API — встроенный в браузер интерфейс для открытия соединения, отправки и приёма сообщений, обработки ошибок и закрытия.
Открытие соединения
WebSocket-соединение создаётся одной строкой:
const socket = new WebSocket('wss://example.com/chat');
Вызов конструктора НЕ ждёт завершения handshake — он возвращает объект сразу. Соединение устанавливается асинхронно. Состояние соединения доступно в socket.readyState:
| readyState | Значение | Описание |
|---|---|---|
WebSocket.CONNECTING | 0 | Соединение устанавливается |
WebSocket.OPEN | 1 | Соединение открыто, можно передавать данные |
WebSocket.CLOSING | 2 | Соединение закрывается |
WebSocket.CLOSED | 3 | Соединение закрыто |
События WebSocket
WebSocket работает на событиях (event-driven). Четыре главных события:
const socket = new WebSocket('wss://example.com/chat');
socket.onopen = (event) => {
console.log('Соединение открыто! Можно отправлять сообщения.');
socket.send(JSON.stringify({ type: 'join', username: 'Иван' }));
};
socket.onmessage = (event) => {
// event.data содержит данные (строка или Blob)
const message = JSON.parse(event.data);
console.log('Получено сообщение:', message);
renderMessage(message);
};
socket.onerror = (event) => {
console.error('Ошибка WebSocket:', event);
};
socket.onclose = (event) => {
console.log(`Соединение закрыто. Код: ${event.code}, Причина: ${event.reason}`);
if (event.code !== 1000) {
// Нештатное закрытие — возможно, переподключиться
setTimeout(() => reconnect(), 3000);
}
};
Порядок событий гарантирован: сначала onopen, потом onmessage (сколько угодно раз), потом onclose. onerror может произойти в любой момент до onclose.
Отправка сообщений
Метод socket.send(data) принимает строку, ArrayBuffer или Blob:
// Текст (формат text, opcode=1)
socket.send('Привет!');
// JSON — самый популярный формат
socket.send(JSON.stringify({
type: 'message',
room: 'general',
text: 'Всем привет!'
}));
// Бинарные данные (opcode=2)
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt32(0, 42);
socket.send(buffer);
send() не блокирует выполнение. Данные ставятся в очередь и отправляются асинхронно. Если соединение ещё не открыто (readyState !== OPEN) — send() бросит исключение.
Формат сообщений: как договариваются клиент и сервер
WebSocket не навязывает формат данных. Ты получаешь строку или бинарный буфер и сам решаешь, как интерпретировать. На практике почти всегда используют JSON:
// Клиент → Сервер: присоединиться к комнате
socket.send(JSON.stringify({ type: 'join', room: 'general' }));
// Сервер → Клиент: новое сообщение
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'new_message') {
showMessage(msg.user, msg.text);
} else if (msg.type === 'user_joined') {
showJoinNotification(msg.user);
}
};
Поле type в JSON — распространённый паттерн для различения видов сообщений. По сути, ты строишь свой мини-протокол поверх WebSocket'а.
Переподключение и обработка разрывов
WebSocket-соединение может разорваться в любой момент: пропала сеть, сервер перезагрузился, прокси разорвал idle-соединение. Реальный код всегда включает логику переподключения:
let socket;
let reconnectAttempts = 0;
function connect() {
socket = new WebSocket('wss://example.com/chat');
socket.onopen = () => {
console.log('Подключено');
reconnectAttempts = 0; // Сброс счётчика
};
socket.onclose = (event) => {
if (event.code === 1000) return; // Нормальное закрытие — не переподключаемся
// Экспоненциальная задержка: 1с, 2с, 4с, 8с... максимум 30с
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
reconnectAttempts++;
console.log(`Переподключение через ${delay / 1000}с (попытка ${reconnectAttempts})`);
setTimeout(connect, delay);
};
}
Экспоненциальная задержка (exponential backoff) — стандартный подход: с каждой неудачей ждём дольше. Без этого сервер может быть завален лавиной переподключений после восстановления (thundering herd).
Готовность и буферизация
WebSocket имеет свойство bufferedAmount — количество байт, ожидающих отправки (в очереди, но ещё не переданных по сети):
// Не отправлять слишком много, пока очередь не очистится
if (socket.bufferedAmount < 1024 * 1024) {
socket.send(largeData);
} else {
console.log('Буфер заполнен, ждём drain');
}
Для большинства приложений это не нужно, но при передаче больших файлов через WebSocket контроль буфера предотвращает переполнение памяти.
Проверь себя
- Почему
send()может бросить исключение? - Что такое exponential backoff и зачем он нужен при переподключении?
- В каком формате обычно передают данные через WebSocket?
- Если соединение ещё не готово (readyState !== OPEN) или уже закрыто. Нужно дождаться события
onopen. - Exponential backoff — увеличение задержки переподключения вдвое с каждой неудачей (1с → 2с → 4с → ...). Предотвращает лавину запросов к восстанавливающемуся серверу.
- JSON — самый популярный формат. Поле
typeразличает виды сообщений. Можно также передавать бинарные данные (ArrayBuffer, Blob).
Что унести с урока
new WebSocket(url)открывает соединение асинхронно. События:onopen,onmessage,onerror,onclose.send()принимает строки и бинарные данные. JSON — стандарт для структурированных сообщений.- WebSocket не переподключается сам — нужна ручная логика с exponential backoff.
readyStateпоказывает текущее состояние: CONNECTING → OPEN → CLOSING → CLOSED.
В завершающем уроке модуля сравним WebSocket vs SSE vs Long Polling — когда что использовать и какие компромиссы.