Перейти к содержанию
Материалы раздела

WebSocket Client

Модуль клиента WebSocket для подключения к удаленным WebSocket/WSS серверам. Поддерживает как незащищенные (ws://), так и защищенные (wss://) соединения.

Особенности

  • Поддержка WebSocket (ws://) и WebSocket Secure (wss://)
  • Автоматический handshake по RFC 6455
  • Маскирование клиентских данных (соответствует стандарту)
  • Автоматическое переподключение при разрыве соединения
  • Активный PING/PONG мониторинг (отправка PING каждые 10 секунд)
  • Обнаружение мертвых соединений (таймаут PONG 15 секунд)
  • Автоматическая фрагментация больших сообщений (>2KB)
  • Очередь входящих сообщений
  • До 4 одновременных подключений
  • Неблокирующее получение данных

websocket.connect(url [, auto_reconnect [, ca_cert]])

Подключается к удаленному WebSocket серверу.

Аргументы:

  • url: URL сервера в формате ws://host:port/path или wss://host:port/path
  • auto_reconnect: включить автоматическое переподключение. По умолчанию false.
  • ca_cert: для wss:// — PEM-сертификат, PEM-bundle или путь к PEM-файлу в файловой системе устройства. Аргумент обязателен, если проверка сертификата включена. Размер источника — не более 64 КиБ.

Возвращает:

  • handle (число) — непрозрачный дескриптор соединения при успехе. Передавайте его функциям модуля без изменений.
  • nil, error (строка) - при ошибке

Модуль не содержит встроенного хранилища доверенных CA. Для WSS передайте собственный актуальный корневой сертификат или bundle. В отладочных сценариях проверку можно отключить через net.skip_ssl_verify(true), но это делает соединение уязвимым для атаки посредника.

Дескриптор содержит поколение внутреннего слота. После websocket.close() он становится недействительным: даже если слот займёт новое соединение, старый дескриптор не сможет отправить, получить или закрыть его данные.

Для сборки модуль должен быть включён параметром CONFIG_LUA_RTOS_LUA_USE_WEBSOCKET. Поддержка WSS управляется отдельным параметром CONFIG_LUA_RTOS_WEBSOCKET_SSL.

local websocket = require("websocket")

-- Подключение без автопереподключения
local handle, err = websocket.connect("ws://echo.websocket.org")
if not handle then
    print("Connection failed: " .. err)
    return
end
print("Connected! Handle: " .. handle)

-- Подключение с автоматическим переподключением
local handle = websocket.connect(
    "wss://api.example.com:443/socket",
    true,
    "/certs/root-ca.pem"
)
if handle then
    print("Connected with auto-reconnect enabled")
end

websocket.send(handle, data)

Отправляет текстовое сообщение на сервер.

Аргументы:

  • handle: дескриптор, возвращенный websocket.connect().
  • data: текстовые данные для отправки (строка)

Возвращает:

  • true при успехе
  • false, error (строка) - при ошибке
local websocket = require("websocket")

local handle = websocket.connect("ws://echo.websocket.org")

-- Отправка сообщения
local ok, err = websocket.send(handle, "Hello WebSocket!")
if not ok then
    print("Send failed: " .. err)
end

-- Отправка JSON данных
local cjson = require("cjson")
local data = {temperature = 25.5, humidity = 60}
websocket.send(handle, cjson.encode(data))

websocket.recv(handle [, timeout_ms])

Получает сообщение с сервера (неблокирующий или с таймаутом).

Аргументы:

  • handle: дескриптор соединения.
  • timeout_ms: необязательный таймаут в миллисекундах (по умолчанию 0 - неблокирующий)

Возвращает:

  • message (строка) - полученное сообщение
  • nil - если сообщений нет
local websocket = require("websocket")

local handle = websocket.connect("ws://echo.websocket.org")

-- Неблокирующее получение
local msg = websocket.recv(handle)
if msg then
    print("Received: " .. msg)
else
    print("No messages")
end

-- Блокирующее получение с таймаутом 5 секунд
local msg = websocket.recv(handle, 5000)
if msg then
    print("Received: " .. msg)
else
    print("Timeout or no messages")
end

websocket.is_connected(handle)

Проверяет, активно ли соединение.

Аргументы:

  • handle: дескриптор соединения.

Возвращает: true если подключен, false иначе

local websocket = require("websocket")

local handle = websocket.connect("ws://echo.websocket.org")

if websocket.is_connected(handle) then
    print("Still connected")
    websocket.send(handle, "ping")
else
    print("Disconnected")
end

websocket.close(handle)

Закрывает WebSocket соединение.

Аргументы:

  • handle: дескриптор соединения.

Возвращает: ничего

local websocket = require("websocket")

local handle = websocket.connect("ws://echo.websocket.org")
-- ... работа с соединением ...
websocket.close(handle)
print("Connection closed")

Полный пример: Echo клиент

local websocket = require("websocket")

-- Подключение
local handle, err = websocket.connect("ws://echo.websocket.org")
if not handle then
    print("Error: " .. err)
    return
end

print("Connected to echo server")

-- Отправка сообщения
websocket.send(handle, "Hello, WebSocket!")

-- Ожидание ответа (5 секунд)
local msg = websocket.recv(handle, 5000)
if msg then
    print("Echo response: " .. msg)
else
    print("No response received")
end

-- Закрытие
websocket.close(handle)

Пример: Отправка данных датчиков с автопереподключением

local websocket = require("websocket")
local cjson = require("cjson")

-- Подключение к серверу мониторинга с автопереподключением
local handle = websocket.connect(
    "wss://monitoring.example.com/sensors",
    true,
    "/certs/root-ca.pem"
)

if not handle then
    print("Connection failed")
    return
end

-- Отправка данных каждые 10 секунд
-- При разрыве соединения автоматически переподключится
while true do
    -- Проверяем статус подключения
    if websocket.is_connected(handle) then
        local data = {
            device_id = "esp32-001",
            temperature = 25.5,
            humidity = 60,
            timestamp = os.time()
        }

        local json = cjson.encode(data)
        local ok = websocket.send(handle, json)

        if not ok then
            print("Send failed, waiting for auto-reconnect...")
        end

        -- Проверка команд с сервера
        local msg = websocket.recv(handle, 100)
        if msg then
            print("Server command: " .. msg)
            local cmd = cjson.decode(msg)
            if cmd.action == "reboot" then
                os.reboot()
            end
        end
    else
        print("Disconnected, auto-reconnect in progress...")
    end

    tmr.delayms(10000)
end

websocket.close(handle)

Пример: Двусторонняя связь

local websocket = require("websocket")
local thread = require("thread")

local handle, err = websocket.connect(
    "wss://chat.example.com/room",
    false,
    "/certs/root-ca.pem"
)
if not handle then
    error(err)
end

-- Поток приема сообщений
thread.start(function()
    while websocket.is_connected(handle) do
        local msg = websocket.recv(handle, 1000)
        if msg then
            print("Received: " .. msg)
        end
    end
end)

-- Основной поток отправки
while websocket.is_connected(handle) do
    local input = io.read()
    if input == "quit" then
        break
    end
    websocket.send(handle, input)
end

websocket.close(handle)

Надежность соединения

Модуль включает несколько механизмов для обеспечения надежности:

Активный PING/PONG мониторинг

  • Автоматически отправляет PING каждые 10 секунд
  • Ожидает PONG в течение 15 секунд
  • При отсутствии PONG отключается (триггер для автопереподключения)
  • Помогает обнаруживать мертвые соединения через NAT/прокси

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

При включении автопереподключения (websocket.connect(url, true)):

  • При обрыве соединения автоматически пытается переподключиться
  • Задержка между попытками: 5 секунд
  • Продолжает попытки до явного вызова websocket.close()
  • Все отправленные сообщения во время разрыва будут потеряны
-- Включение автопереподключения
local handle = websocket.connect(
    "wss://server.com/socket",
    true,
    "/certs/root-ca.pem"
)

-- Соединение будет автоматически восстанавливаться при разрыве
while true do
    if websocket.is_connected(handle) then
        websocket.send(handle, "heartbeat")
    else
        -- Переподключение происходит автоматически
        print("Waiting for reconnection...")
    end
    tmr.delayms(5000)
end

Фрагментация больших сообщений

  • Сообщения >2048 байт автоматически разбиваются на фреймы
  • Соответствует RFC 6455 (continuation frames)
-- Отправка большого JSON (например, 10KB)
local large_data = cjson.encode(big_table)
websocket.send(handle, large_data)  -- Автоматически фрагментируется

Ограничения и параметры

  • Максимум 4 одновременных подключения
  • Максимальный размер входящего сообщения: 2048 байт
  • Размер очереди сообщений: 10 сообщений на клиента
  • Входящие TEXT- и BINARY-фреймы возвращаются как строки Lua. Тип фрейма отдельно не возвращается.
  • Исходящие сообщения автоматически фрагментируются по 2048 байт
  • Интервал отправки PING: 10 секунд
  • Таймаут ожидания PONG: 15 секунд
  • Задержка переподключения: 5 секунд

Обработка ошибок

local websocket = require("websocket")

local handle, err = websocket.connect("ws://invalid-server.com")
if not handle then
    print("Connection error: " .. err)
    -- Возможные ошибки:
    -- "Invalid URL scheme"
    -- "Invalid port number"
    -- "DNS lookup failed"
    -- "Socket creation failed"
    -- "Connection failed"
    -- "CA certificate is required for WSS"
    -- "CA certificate load failed"
    -- "TLS handshake failed"
    -- "WebSocket handshake read failed"
    -- "No free client slots"
    return
end

local ok, err = websocket.send(handle, "test")
if not ok then
    print("Send error: " .. err)
    -- Возможные ошибки:
    -- "Invalid WebSocket handle"
    -- "Client not connected"
    -- "Send failed"
end