WebSocket-ответы
Формирование и отправка кадров обратно клиенту через WebSocket-соединение. Ответ доступен через объект wsctx_t->response (websocketsresponse_t*). Методы ответа — это указатели на функции, навешанные на объект, поэтому первым аргументом всегда передаётся сам объект.
#include "websockets.h"
void my_handler(wsctx_t* ctx) {
ctx->response->send_text(ctx->response, "Hello World");
}Протокол WebSocket передаёт данные кадрами двух содержательных типов — текстовые (opcode 0x81) и бинарные (opcode 0x82). Фреймворк предоставляет методы для каждого формата, а также общий метод, который выбирает формат по типу входящего запроса.
Когда отправляется ответ
Кадр формируется и ставится в очередь отправки только при вызове одного из методов send_*. Пока метод не вызван, клиенту ничего не уходит — поэтому в конце обработчика обязательно нужно вызвать send_text/send_binary/send_data/send_file (или управляющий кадр). Если ни один метод не вызвать, клиент не получит ответа по этому запросу.
После отправки ответа обработчик должен завершиться (return) — повторная отправка в рамках одного запроса сформирует некорректный кадр.
Обзор методов
| Метод | Аргументы | Описание |
|---|---|---|
send_text | (response, data) | Текстовый кадр, null-terminated строка |
send_textn | (response, data, length) | Текстовый кадр с явной длиной |
send_binary | (response, data) | Бинарный кадр, длина через strlen |
send_binaryn | (response, data, length) | Бинарный кадр с явной длиной |
send_data | (ctx, data) | Кадр в формате запроса (текст/бинарный) |
send_datan | (ctx, data, length) | То же с явной длиной |
send_file | (response, path) | Файл бинарным кадром, путь относительно корня |
send_filen | (response, path, length) | То же с явной длиной пути |
Первый аргумент отличается
Большинство методов принимают объект ответа — ctx->response. Исключение — send_data/send_datan: им нужен весь контекст ctx, чтобы прочитать тип входящего запроса. Поэтому вызов выглядит как ctx->response->send_data(ctx, ...).
Текстовые сообщения
send_text отправляет null-terminated строку текстовым кадром. Если данные содержат встроенные нули или их длина известна заранее, используйте вариант send_textn с явной длиной:
void get(wsctx_t* ctx) {
ctx->response->send_text(ctx->response, "{\"message\": \"This is json\"}");
}
void getn(wsctx_t* ctx) {
const char data[] = { 'H', 'i', 0x00, '!' };
ctx->response->send_textn(ctx->response, data, sizeof(data));
}Бинарные сообщения
send_binary отправляет бинарный кадр. Важно: этот вариант определяет длину через strlen(), поэтому он небезопасен для данных со встроенными нулями — всё после первого нулевого байта будет потеряно. Для настоящего бинарного содержимого используйте send_binaryn с явной длиной:
// Безопасно для текста без нулей
void get(wsctx_t* ctx) {
ctx->response->send_binary(ctx->response, "Text in binary format");
}
// Безопасно для произвольных байтов
void raw(wsctx_t* ctx) {
const char data[] = { 0x00, 0x01, 0x02, 0x03, 0x00, 0xFF };
ctx->response->send_binaryn(ctx->response, data, sizeof(data));
}Автоматический формат
Метод send_data определяет тип входящего запроса и отправляет ответ в том же формате: если запрос был текстовым — уходит текстовый кадр, иначе бинарный. Удобно для echo-обработчиков и случаев, когда формат ответа должен совпадать с форматом запроса.
void echo(wsctx_t* ctx) {
ctx->response->send_data(ctx, "echo"); // null-terminated
ctx->response->send_datan(ctx, "echo", 4); // с явной длиной
}Один формат на соединение
WebSocket не требует, чтобы запрос и ответ были в одном формате, но на практике удобнее держать их согласованными — так клиентский парсер всегда знает, чего ждать. send_data снимает с разработчика ручную проверку типа запроса.
Отправка файлов
send_file читает файл и отправляет его одним бинарным кадром. Путь указывается относительно корня сервера (server.root из config.json), аналог с явной длиной пути — send_filen:
void get(wsctx_t* ctx) {
ctx->response->send_file(ctx->response, "files/image.jpg");
}Метод возвращает 0 при успехе и -1 при ошибке. При этом ошибочный случай не остаётся без ответа: если файл не найден или это каталог, автоматически отправляется текстовое сообщение ("resource not found" / "resource forbidden"). Отдельно вызывать send_text или send_binary вместе с send_file не нужно — это лишь повредит кадр.
void get(wsctx_t* ctx) {
int result = ctx->response->send_file(ctx->response, "files/report.pdf");
if (result == -1) {
// Файл не найден или недоступен — текстовый кадр с ошибкой уже отправлён.
return;
}
}Управляющие кадры
Помимо текстовых и бинарных, протокол определяет управляющие кадры. Для их отправки используются свободные функции (не методы объекта), объявленные в websocketsresponse.h:
| Функция | Opcode | Назначение |
|---|---|---|
websocketsresponse_pong(response, data, length) | 0x8A | Ответ на входящий PING; обычно эхо полезной нагрузки пинга |
websocketsresponse_close(response, data, length) | 0x88 | Инициировать закрытие соединения; data — необязательная причина/код |
void close_handler(wsctx_t* ctx) {
// Закрыть соединение с кодом 1000 (Normal Closure)
const char reason[] = { 0x03, 0xE8 }; // 1000 в big-endian
websocketsresponse_close(ctx->response, reason, sizeof(reason));
}Управляющие кадры — низкоуровневый API
Обычно фреймворк сам отвечает на PING кадром PONG и согласует закрытие. websocketsresponse_pong / websocketsresponse_close нужны только для ручного управления жизненным циклом соединения.
Сжатие (permessage-deflate)
Если соединение согласовало расширение permessage-deflate (RFC 7692), фреймворк автоматически сжимает исходящие текстовые и бинарные кадры размером от 128 байт. В сжатом кадре устанавливается бит RSV1, а длина полезной нагрузки кодируется как обычно.
Сжатие включается только когда оно действительно уменьшает размер; иначе кадр уходит несжатым. Если сжатие завершается ошибкой, фреймворк также откатывается к несжатой отправке — обработчику ничего проверять не нужно.
Настройка согласования расширения описана в конфигурации WebSocket-маршрутов (config.json).
Важно
- Клиенту ничего не отправляется, пока не вызван один из
send_*(send_text,send_textn,send_binary,send_binaryn,send_data,send_datan,send_file,send_filen) или управляющая функция (websocketsresponse_pong/websocketsresponse_close). - После отправки ответа завершайте обработчик через
return— повторная отправка в одном запросе сформирует некорректный кадр. send_binaryвычисляет длину черезstrlen()— для бинарных данных со встроенными нулями используйтеsend_binaryn.send_file/send_filenотправляют файл относительно корня сервера и сами формируют кадр-сообщение при ошибке доступа, дополнительная отправка не требуется.- Получение данных от клиента описано в разделе Получение данных от клиента; широковещательная рассылка подписчикам — в Широковещание.