Перейти к содержимому

Жизненный цикл соединения

От первого открытия WebSocket до стабильной работы клиент проходит несколько фаз. Эта страница — референс для написания устойчивого клиента, который сам восстанавливается после дисконнектов.

┌──────────────────┐
│ connect(ws://...)│
└────────┬─────────┘
┌──────────────────┐ HELLO (op 10)
│ сервер → HELLO │◀───────────────
│ heartbeat_ │
│ interval │
└────────┬─────────┘
├─── запускаем heartbeat-таймер
┌──────────────────┐ IDENTIFY (op 2) или RESUME (op 6)
│ клиент → IDENTIFY│───────────────▶
└────────┬─────────┘
┌──────────────────┐ READY (op 0, t=READY)
│ сервер → READY │◀───────────────
│ session_id, │
│ user, guilds │
└────────┬─────────┘
┌──────────────────┐
│ нормальная работа│ ← dispatch-события, heartbeat каждые N мс
│ │
└────────┬─────────┘
┌──────────┼──────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
close INVALID_ RECONNECT heartbeat
code SESSION (op 7) ACK пропал
(op 9)
│ │ │ │
└────┬─────┴──────────┴──────────────┘
реконнект → RESUME если session_id жив, иначе IDENTIFY

Сразу после подключения сервер шлёт опкод 10 (HELLO):

{
"op": 10,
"d": { "heartbeat_interval": 41250 }
}

heartbeat_interval в миллисекундах. Клиент запускает таймер, который каждые N мс отправляет heartbeat (см. ниже).

Клиент отправляет опкод 2 с токеном и информацией о клиенте:

{
"op": 2,
"d": {
"token": "Bot MTIzNDU2...",
"properties": {
"os": "linux",
"browser": "mybot",
"device": "mybot"
},
"presence": null,
"ignored_events": [],
"initial_guild_id": null,
"flags": 0
}
}
ПолеОбязательноОписание
tokenДаСтрока вида Bot <token> для ботов или «сырой» user-token для первой стороны
properties.osДаПроизвольная строка, например linux
properties.browserДаИмя вашего приложения, например mybot
properties.deviceДаИмя устройства/инстанса
presenceНетНачальный presence — см. PRESENCE_UPDATE
ignored_eventsНетМассив строк — типы событий, которые не получать. По умолчанию приходит всё
initial_guild_idНетID гильдии, с которой начать синхронизацию, если у клиента их много
flagsНетБитовое поле фич — по умолчанию 0

Если токен не валиден — сервер закроет соединение с кодом 4004 AUTHENTICATION_FAILED. Реконнектиться при этом нельзя — получите ban-эскалацию.

При успешном IDENTIFY сервер шлёт событие READY (op: 0, t: "READY"):

{
"op": 0,
"s": 1,
"t": "READY",
"d": {
"v": 1,
"user": { "id": "149...", "username": "my_bot", "bot": true, "flags": 0 },
"session_id": "a1b2c3d4...",
"resume_gateway_url": "wss://gateway.floodilka.com",
"guilds": [
{ "id": "142...", "unavailable": true }
],
"user_settings": { ... }
}
}

Критичные поля, которые надо сохранить:

  • session_id — потребуется для RESUME
  • resume_gateway_url — использовать вместо дефолтного URL при RESUME
  • s из оболочки (1 в примере) — последний номер события в сессии

Флаг unavailable: true у гильдии означает, что полные данные ещё не загружены. Дождитесь событий GUILD_CREATE — они прилетят в течение секунд после READY.

Клиент обязан каждые heartbeat_interval мс отправлять опкод 1 с последним виденным s:

{ "op": 1, "d": 42 }

Если s ещё не был получен (сразу после HELLO) — d: null.

В ответ сервер шлёт 11 (HEARTBEAT_ACK):

{ "op": 11 }

Сервер может сам прислать 1 (попросить клиент отправить heartbeat сейчас) — в этом случае отвечайте немедленно, не дожидаясь таймера.

После READY начинают приходить dispatch-сообщения:

{
"op": 0,
"s": 43,
"t": "MESSAGE_CREATE",
"d": { "id": "...", "channel_id": "...", "content": "hi", "author": { ... } }
}

Обновляйте s из каждого такого сообщения — он потребуется для heartbeat и RESUME.

Полный список типов — в разделе События.

Соединение может разорваться тремя способами:

Сервер прислал опкод 7 без d — просит клиента переподключиться и возобновить сессию:

{ "op": 7, "d": null }

Алгоритм:

  1. Закрыть WebSocket с кодом 4000 (чтобы сервер не сбрасывал сессию)
  2. Подключиться заново к resume_gateway_url (из READY)
  3. Дождаться HELLO, запустить heartbeat
  4. Отправить RESUME (op 6) вместо IDENTIFY

Сервер прислал опкод 9:

{ "op": 9, "d": true }
  • d: true — сессия жива, можно попробовать RESUME
  • d: false — сессия мертва, надо новый IDENTIFY (не RESUME)

В обоих случаях подождите 1-5 секунд с рандомным jitter’ом перед попыткой — не спамьте.

WebSocket закрылся с close code. Справочник — Коды закрытия. Ключевое:

  • 1000, 1001, 4000-4003 (не 4004) — переподключаться безопасно
  • 4004 AUTHENTICATION_FAILED, 4010 INVALID_SHARD, 4011 SHARDING_REQUIRED, 4012 INVALID_API_VERSIONреконнект не поможет. Разбирайтесь с конфигом клиента

RESUME — это IDENTIFY с контекстом предыдущей сессии:

{
"op": 6,
"d": {
"token": "Bot MTIzNDU2...",
"session_id": "a1b2c3d4...",
"seq": 42
}
}

seq — последнее виденное значение s. Сервер «догонит» клиента, отправив пропущенные события начиная с seq + 1, затем отправит RESUMED (op: 0, t: "RESUMED").

Если seq устарел (сервер не держит события так далеко в прошлое) — придёт op: 9 (INVALID_SESSION, d: false), и надо делать IDENTIFY заново. Всё состояние, накопленное в памяти клиента, придётся сбросить.

  1. Подключаемся, ждём HELLO, стартуем heartbeat
  2. Если у нас есть сохранённые session_id и seq — отправляем RESUME. Иначе — IDENTIFY
  3. Ждём READY или RESUMED. Сохраняем session_id и resume_gateway_url
  4. Работаем, обновляем seq из каждого события
  5. При любом дисконнекте:
    • Exponential backoff с jitter (1s, 2s, 4s, …, max 30s)
    • Если текущая попытка — RESUME и она упала с op: 9 → сбрасываем состояние и идём в IDENTIFY
    • Если close code — 4004/4010/4011/4012останавливаем бота, логируем ошибку, не ретраим