Фреймворк Fresh на базе Deno предлагает лёгкий подход к работе с WebSocket. WebSocket в Fresh реализуется через стандартный API Deno, что позволяет устанавливать двунаправленное соединение между клиентом и сервером с минимальной задержкой. Основное преимущество такого подхода — нативная интеграция с серверной частью без необходимости в дополнительных библиотеках или сложной конфигурации.
Соединение WebSocket создаётся в обработчике запроса. Важно
использовать метод Deno.upgradeWebSocket(req), который
возвращает объект с потоками socket и
response. Пример базового подключения:
import { serve } from "https://deno.land/std/http/server.ts";
serve(async (req) => {
const { socket, response } = Deno.upgradeWebSocket(req);
socket.ono pen = () => {
console.log("WebSocket соединение установлено");
};
socket.onmess age = (event) => {
socket.send(`Сервер получил: ${event.data}`);
};
socket.oncl ose = () => {
console.log("Соединение закрыто");
};
return response;
});
socket.send() используется для отправки данных на
клиент, а события onmessage, onopen и
onclose позволяют управлять состоянием соединения.
При увеличении количества клиентов простое однопоточное решение быстро становится узким местом. Для масштабирования WebSocket важно учитывать несколько аспектов:
Горизонтальное масштабирование — запуск нескольких инстансов приложения на разных серверах или контейнерах. Необходимо синхронизировать состояние соединений между экземплярами.
Использование брокеров сообщений — Redis, NATS или Kafka позволяют обмениваться событиями между разными инстансами приложения. Каждый сервер обрабатывает свои соединения, но события реплицируются через брокер.
Балансировка нагрузки — использование прокси типа Nginx или Envoy для распределения WebSocket-запросов между инстансами. Важно включать sticky sessions или идентификацию клиентов по токену, чтобы соединение оставалось на одном сервере.
Пример интеграции Redis Pub/Sub для синхронизации сообщений между инстансами:
import { connect } from "https://deno.land/x/redis/mod.ts";
const redis = await connect({ hostname: "127.0.0.1", port: 6379 });
const pub = redis.publish;
const sub = await redis.subscribe("messages");
sub.on("message", (channel, message) => {
// Рассылка сообщения всем локальным клиентам
for (const socket of connectedSockets) {
socket.send(message);
}
});
Для масштабируемых приложений важно хранить активные соединения в структуре данных, которая позволяет быстро добавлять и удалять клиентов. В Fresh это обычно реализуется через Map:
const connectedSockets = new Map();
socket.ono pen = () => {
connectedSockets.set(socket, { id: clientId });
};
socket.oncl ose = () => {
connectedSockets.delete(socket);
};
Использование Map обеспечивает быструю итерацию и отправку сообщений группам клиентов.
Для эффективной работы с большим числом соединений применяются различные паттерны:
Пример организации комнат:
const rooms = new Map();
function joinRoom(socket, room) {
if (!rooms.has(room)) rooms.set(room, new Set());
rooms.get(room).add(socket);
}
function leaveRoom(socket, room) {
if (rooms.has(room)) {
rooms.get(room).delete(socket);
if (rooms.get(room).size === 0) rooms.delete(room);
}
}
function broadcastRoom(room, message) {
if (rooms.has(room)) {
for (const socket of rooms.get(room)) {
socket.send(message);
}
}
}
При масштабировании WebSocket критически важна устойчивость к сбоям:
onclose и
onerror.Пример простого переподключения на клиенте:
let ws;
function connect() {
ws = new WebSocket("ws://localhost:8000");
ws.ono pen = () => console.log("Соединение установлено");
ws.oncl ose = () => setTimeout(connect, 1000);
}
connect();
Для крупномасштабных приложений сочетание этих методов обеспечивает надёжное и масштабируемое управление WebSocket-соединениями в Fresh.