
Разработка эффективных и безопасных приложений требует не только хорошо продуманного API, но и правильного выбора протокола передачи данных. Веб-приложения обычно используют текстовые форматы, такие как JSON или XML, но для высокопроизводительных систем, требующих минимальной задержки и небольшого объема передачи данных, может быть выгодно использовать бинарные протоколы.
В этой статье мы рассмотрим, как разработать собственный бинарный протокол для приложений на основе Node.js и WebSockets, добавить авторизацию с помощью JWT и изучим преимущества бинарного протокола по сравнению с другими форматами данных.

Почему именно бинарный протокол?
Преимущества бинарного протокола:
Эффективность: Бинарные протоколы более компактны, чем текстовые форматы (например, JSON). Они позволяют передавать данные в более сжатом виде, что уменьшает объем передаваемого трафика.
Производительность: Благодаря меньшему объему данных и отсутствию необходимости в анализе текстовых форматов, бинарные протоколы экономят ресурсы как на стороне клиента, так и на стороне сервера.
Безопасность: Двоичные данные сложнее анализировать в режиме реального времени по сравнению с текстовыми данными, что делает двоичные протоколы менее уязвимыми для атак.
Гибкость: В бинарных протоколах форматы данных можно более точно контролировать для работы с конкретными типами данных (например, числами с плавающей запятой, строками, массивами байтов и т. д.).
Архитектура системы:
Мы разработаем систему, состоящую из следующих компонентов:
Сервер на Node.js, использующий WebSockets для связи с клиентами.
JavaScript -клиент, который подключается к серверу и использует бинарный протокол для передачи данных.
Авторизация с использованием JWT (JSON Web Token) для безопасного подключения клиентов к серверу.
Серверная реализация на Node.js
Установка зависимостей
Для начала установим необходимые зависимости:
npm init -y npm install ws jsonwebtoken
Примечание: JWT можно также заменить на авторизацию внутри бинарного протокола, однако здесь будем использовать его для простоты понимания
ws — это библиотека для работы с WebSocket на стороне сервера, а jsonwebtoken — для работы с JWT.
Простой серверный код:
const WebSocket = require('ws'); const jwt = require('jsonwebtoken'); // Наш секретный ключ для JWT const SECRET_KEY = 'your_secret_key'; // Создаем сервер const wss = new WebSocket.Server({ port: 8080 }); // Верификация токена function verifyJWT(token) { try { return jwt.verify(token, SECRET_KEY); } catch (e) { return null; } } // Подключение к сокету wss.on('connection', (ws, req) => { // Получение токена из Header const token = req.url.split('token=')[1]; const user = verifyJWT(token); // Пользователь не авторизован if (!user) { ws.close(4001, 'Unauthorized'); return; } console.log(`User ${user.username} connected`); ws.on('message', (message) => { if (message instanceof Buffer) { // Рабоа с бинарными сообщениями const messageType = message.readUInt8(0); // Первый байт - тип сообщения if (messageType === 1) { const textLength = message.readUInt16BE(1); const text = message.toString('utf-8', 3, 3 + textLength); console.log(`Received message from ${user.username}: ${text}`); } else if(messageType === 2) { // Другие типы сообщений } } }); ws.on('close', () => { console.log(`User ${user.username} disconnected`); }); }); console.log('WebSocket server started on ws://localhost:8080');
Пояснение к коду:
Авторизация с помощью JWT: Сервер проверяет токен JWT, переданный клиентом при подключении. Если токен недействителен, сервер закрывает соединение с ошибкой авторизации.
Обработка двоичных данных: В этом примере предполагается, что клиент отправляет двоичные данные. Сервер анализирует сообщение, считывая данные побайтно. Например, первый байт сообщения может использоваться в качестве типа сообщения, за которым следуют длина сообщения и сами данные.
Сервер WebSocket: библиотека ws используется для управления соединениями и сообщениями.
Внедрение клиентских решений
Клиентский код
Для реализации клиентской части мы используем чистый JavaScript.
// Создаем подключение к серверу const socket = new WebSocket('ws://localhost:8080?token=your_jwt_token'); // Подключение установлено socket.addEventListener('open', () => { console.log('Connected to server'); // Пример бинарного сообщения const message = "Hello, Binary World!"; const buffer = new ArrayBuffer(3 + message.length); const view = new DataView(buffer); view.setUint8(0, 1); // Устанавливаем тип сообщения view.setUint16(1, message.length); // Устанавливаем длинну for (let i = 0; i < message.length; i++) { view.setUint8(3 + i, message.charCodeAt(i)); } socket.send(buffer); }); // Получание сообщение от сервера socket.addEventListener('message', (event) => { if (event.data instanceof Blob) { event.data.arrayBuffer().then(buffer => { const view = new DataView(buffer); const messageType = view.getUint8(0); if (messageType === 1) { // Тип сообщения const textLength = view.getUint16(1); const text = String.fromCharCode(...new Uint8Array(buffer.slice(3, 3 + textLength))); console.log(`Received message: ${text}`); } }); } }); // Закрытие соединения socket.addEventListener('close', () => { console.log('Disconnected from server'); });
Пояснение к коду:
Подключение к серверу: Клиент подключается к WebSocket серверу, передавая JWT-токен через строку запроса.
Отправка двоичных данных: Для отправки двоичных данных создается объект ArrayBuffer, в который записываются тип сообщения и текстовые данные.
Получение сообщений: Клиент ожидает от сервера двоичные данные и анализирует их, используя DataView для чтения байтов.
Создание и проверка JWT-токенов
Пример создания JWT-токена на стороне сервера:
const jwt = require('jsonwebtoken'); const SECRET_KEY = 'your_secret_key'; const token = jwt.sign({ username: 'user1' }, SECRET_KEY, { expiresIn: '1h' }); console.log(token);
Этот токен можно использовать для подключения клиента.
Заключение
Использование бинарного протокола в сочетании с WebSockets и авторизацией через JWT позволяет создать эффективную и безопасную систему для взаимодействия клиента и сервера. Бинарные протоколы, несмотря на сложность их реализации, обеспечивают значительные преимущества в производительности и сокращении объёма данных. Они особенно актуальны для приложений с высокой нагрузкой и интенсивным использованием ресурсов, где минимизация задержки и загрузки сети имеют важное значение.
Примеры готовых библиотек:
Этот подход может быть полезен для разработки игр, приложений с синхронизацией в реальном времени, финансовых приложений и других систем, требующих высокой производительности и надежности.
И, конечно же, спасибо за прочтение.
