Skip to content

WebSocket Broadcasting

Broadcasting позволяет отправлять сообщения группам клиентов в реальном времени. Это основа для создания чатов, уведомлений и live-обновлений.

Концепция

  • Канал — именованная группа WebSocket-соединений
  • Клиент — соединение, подключённое к каналу
  • Структура идентификации — данные для фильтрации получателей
  • Обработчик ответа — функция формирования сообщения

Конфигурация

json
"websockets": {
    "routes": {
        "/channel-join": {
            "GET": { "file": "handlers/libws.so", "function": "channel_join" }
        },
        "/channel-leave": {
            "GET": { "file": "handlers/libws.so", "function": "channel_leave" }
        },
        "/channel-send": {
            "POST": { "file": "handlers/libws.so", "function": "channel_send" }
        }
    }
}

Базовое использование

Подключение к каналу

c
#include "websockets.h"
#include "broadcast.h"

void channel_join(wsctx_t* ctx) {
    broadcast_add("notifications", ctx->request->connection, NULL, broadcast_send_text);
    ctx->response->send_text(ctx->response, "Joined channel");
}

Отключение от канала

c
void channel_leave(wsctx_t* ctx) {
    broadcast_remove("notifications", ctx->request->connection);
    ctx->response->send_text(ctx->response, "Left channel");
}

Отправка всем в канале

c
void channel_send(wsctx_t* ctx) {
    char* message = websocketsrequest_payload(ctx->request->protocol);

    if (!message) {
        ctx->response->send_text(ctx->response, "No message");
        return;
    }

    // Отправка всем, кроме отправителя
    broadcast_send_all("notifications", ctx->request->connection, message, strlen(message));

    free(message);
    ctx->response->send_text(ctx->response, "Message sent");
}

Обработчик ответа

Функция формирования ответа для клиентов канала:

c
void broadcast_send_text(response_t* response, const char* data, size_t size) {
    websocketsresponse_t* ws_response = (websocketsresponse_t*)response;
    ws_response->send_textn(ws_response, data, size);
}

void broadcast_send_binary(response_t* response, const char* data, size_t size) {
    websocketsresponse_t* ws_response = (websocketsresponse_t*)response;
    ws_response->send_binaryn(ws_response, data, size);
}

Фильтрация получателей

Для отправки сообщений определённым клиентам используйте структуру идентификации.

Определение структуры

c
// broadcasting/chat_broadcast.h
typedef struct chat_broadcast_id {
    broadcast_id_t base;    // Обязательное базовое поле
    int user_id;
    int room_id;
} chat_broadcast_id_t;

chat_broadcast_id_t* chat_broadcast_id_create(int user_id, int room_id);
void chat_broadcast_id_free(void* id);
c
// broadcasting/chat_broadcast.c
#include "chat_broadcast.h"

chat_broadcast_id_t* chat_broadcast_id_create(int user_id, int room_id) {
    chat_broadcast_id_t* id = malloc(sizeof(chat_broadcast_id_t));
    if (!id) return NULL;

    id->base.free = chat_broadcast_id_free;
    id->user_id = user_id;
    id->room_id = room_id;

    return id;
}

void chat_broadcast_id_free(void* ptr) {
    if (ptr) free(ptr);
}

Подключение с идентификацией

c
#include "query.h"

void join_room(wsctx_t* ctx) {
    int ok = 0;
    const char* room_id_str = query_param_char(ctx->request, "room", &ok);
    const char* user_id_str = query_param_char(ctx->request, "user", &ok);

    if (!ok) {
        ctx->response->send_text(ctx->response, "Missing parameters");
        return;
    }

    int room_id = atoi(room_id_str);
    int user_id = atoi(user_id_str);

    chat_broadcast_id_t* id = chat_broadcast_id_create(user_id, room_id);
    if (!id) {
        ctx->response->send_text(ctx->response, "Memory error");
        return;
    }

    broadcast_add("chat", ctx->request->connection, (broadcast_id_t*)id, broadcast_send_text);
    ctx->response->send_text(ctx->response, "Joined room");
}

Функция сравнения

c
int compare_by_room(void* source, void* target) {
    chat_broadcast_id_t* src = (chat_broadcast_id_t*)source;
    chat_broadcast_id_t* tgt = (chat_broadcast_id_t*)target;

    return src->room_id == tgt->room_id;
}

int compare_by_user(void* source, void* target) {
    chat_broadcast_id_t* src = (chat_broadcast_id_t*)source;
    chat_broadcast_id_t* tgt = (chat_broadcast_id_t*)target;

    return src->user_id == tgt->user_id;
}

Отправка с фильтрацией

c
#include "query.h"

void send_to_room(wsctx_t* ctx) {
    int ok = 0;
    const char* room_id_str = query_param_char(ctx->request, "room", &ok);

    if (!ok) {
        ctx->response->send_text(ctx->response, "Missing room parameter");
        return;
    }

    char* message = websocketsrequest_payload(ctx->request->protocol);

    if (!message) {
        ctx->response->send_text(ctx->response, "No message");
        return;
    }

    // Создаём фильтр для комнаты
    chat_broadcast_id_t* filter = chat_broadcast_id_create(0, atoi(room_id_str));

    // Отправляем только клиентам в указанной комнате
    broadcast_send("chat", ctx->request->connection, message, strlen(message),
                   (broadcast_id_t*)filter, compare_by_room);

    free(message);
    ctx->response->send_text(ctx->response, "Message sent to room");
}

API Broadcasting

ФункцияОписание
broadcast_add(channel, conn, id, handler)Добавить клиента в канал
broadcast_remove(channel, conn)Удалить клиента из канала
broadcast_send_all(channel, sender, data, len)Отправить всем (кроме sender)
broadcast_send(channel, sender, data, len, filter, cmp)Отправить с фильтрацией

Пример: Чат-комната

Сервер

c
#include "websockets.h"
#include "broadcast.h"
#include "json.h"
#include "query.h"

void ws_join_chat(wsctx_t* ctx) {
    int ok = 0;
    const char* username = query_param_char(ctx->request, "username", &ok);

    if (!ok) {
        ctx->response->send_text(ctx->response, "Missing username parameter");
        return;
    }

    chat_user_t* user = chat_user_create(username);
    broadcast_add("global_chat", ctx->request->connection,
                  (broadcast_id_t*)user, broadcast_send_text);

    // Уведомление о входе
    json_doc_t* doc = json_init();
    json_token_t* root = json_create_object(doc);
    json_object_set(root, "type", json_create_string(doc, "user_joined"));
    json_object_set(root, "username", json_create_string(doc, username));

    const char* json = json_stringify(doc);
    broadcast_send_all("global_chat", ctx->request->connection, json, strlen(json));
    json_free(doc);

    ctx->response->send_text(ctx->response, "Welcome to chat");
}

void ws_chat_message(wsctx_t* ctx) {
    char* message = websocketsrequest_payload(ctx->request->protocol);
    if (!message) return;

    broadcast_send_all("global_chat", ctx->request->connection, message, strlen(message));
    free(message);
}

Клиент (JavaScript)

javascript
const ws = new WebSocket("wss://example.com/wss", "resource");

ws.onopen = () => {
    ws.send("GET /join-chat?username=Alice");
};

ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log("Message:", data);
};

// Отправка сообщения
function sendMessage(text) {
    ws.send(`POST /chat-message ${JSON.stringify({ text })}`);
}

Важно

  • Память структуры идентификации освобождается автоматически при отключении клиента
  • Клиент-отправитель не получает своё сообщение при broadcast
  • Каналы создаются автоматически при первом broadcast_add
  • Каналы удаляются автоматически когда последний клиент отключается

Выпущено под лицензией MIT.