Learning Book

WebSocket: двусторонняя связь

WebSocket

Базовый WebSocket

// Создание соединения
const ws = new WebSocket('wss://ws.example.com/chat')
// wss:// — защищённый WebSocket (аналог https://)
// ws://  — незащищённый (только для localhost!)

// События
ws.onopen = (event) => {
  console.log('Соединение установлено')
  ws.send('Привет, сервер!')
}

ws.onmessage = (event) => {
  console.log('Получено:', event.data)
  const message = JSON.parse(event.data)
}

ws.onerror = (error) => {
  console.error('Ошибка WebSocket:', error)
}

ws.onclose = (event) => {
  console.log(`Соединение закрыто: ${event.code} — ${event.reason}`)
  // event.wasClean — было ли закрытие нормальным
}

Отправка данных

// Текст
ws.send('Привет!')

// JSON
ws.send(JSON.stringify({ type: 'message', text: 'Привет!' }))

// Бинарные данные
ws.send(arrayBuffer)
ws.send(blob)

// Состояние соединения
ws.readyState
// WebSocket.CONNECTING = 0
// WebSocket.OPEN = 1
// WebSocket.CLOSING = 2
// WebSocket.CLOSED = 3

// Безопасная отправка только если соединение открыто
function safeSend(ws, data) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(typeof data === 'string' ? data : JSON.stringify(data))
  }
}

Автоматическое переподключение

class ReconnectingWebSocket {
  constructor(url, options = {}) {
    this.url = url
    this.maxRetries = options.maxRetries ?? 5
    this.retryDelay = options.retryDelay ?? 1000
    this.retryCount = 0
    this.handlers = {}
    this.connect()
  }

  connect() {
    this.ws = new WebSocket(this.url)

    this.ws.onopen = (e) => {
      this.retryCount = 0  // сбрасываем счётчик
      this.handlers.open?.(e)
    }

    this.ws.onmessage = (e) => {
      this.handlers.message?.(e)
    }

    this.ws.onclose = (e) => {
      this.handlers.close?.(e)
      if (!e.wasClean && this.retryCount < this.maxRetries) {
        const delay = this.retryDelay * Math.pow(2, this.retryCount) // exponential backoff
        this.retryCount++
        console.log(`Переподключение через ${delay}мс (попытка ${this.retryCount})`)
        setTimeout(() => this.connect(), delay)
      }
    }

    this.ws.onerror = (e) => {
      this.handlers.error?.(e)
    }
  }

  on(event, handler) {
    this.handlers[event] = handler
    return this
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data))
    }
  }

  close() {
    this.maxRetries = 0  // отключаем автоматическое переподключение
    this.ws.close(1000, 'Нормальное закрытие')
  }
}
Exponential backoff — задержка удваивается при каждой попытке: 1с, 2с, 4с, 8с... Это предотвращает перегрузку сервера при массовых переподключениях.