Как настроить соединение WebSocket с Node.js и React.js?
С появлением социальных сетей и других средств связи через Интернет обеспечение связи в режиме реального времени становится все более важным. Протокол передачи гипертекста (HTTP) — это протокол связи, подобный WebSocket, который следует шаблону односторонней связи «запрос-ответ». Он использовался большинством разработчиков для реализации связи в реальном времени, но его не хватает, когда вариант использования включает в себя мгновенные обновления и создание полнодуплексного инструмента двунаправленной связи. Хотя HTTP с помощью различных методов, таких как HTTP-опрос, потоковая передача и события, отправляемые сервером (SSE), помог разработчикам реализовать передачу данных, существуют ограничения и недостатки, которые проложили путь для WebSocket. WebSocket помогает разработчикам реализовать механизм полнодуплексной двунаправленной связи и помогает разработчикам создавать системы связи в реальном времени.
В этой статье мы обсудим недостатки протокола HTTP, а также рассмотрим концепцию связи в реальном времени и способы ее реализации с помощью протокола WebSocket при создании базового клиентского приложения для связи.
Почему вебсокет?
При создании веб-приложений с функцией полнодуплексной (двусторонней) двунаправленной связи разработчики должны настроить традиционный протокол HTTP для реализации передачи данных. Протокол HTTP имеет несколько методов, таких как опрос HTTP, потоковая передача HTTP и события, отправляемые сервером, которые помогают разработчикам создавать приложения реального времени. Эти методы имеют ряд недостатков, в которых WebSockets доказали свое превосходство, как мы увидим в следующих нескольких разделах.
HTTP Polling Первой попыткой решить проблемы, возникающие при использовании традиционного протокола HTTP, является опрос интервалов сервера. Клиент отправляет запрос на сервер через заранее определенные интервалы, используя эти функции setInterval или setTimeout. При длительном опросе сервер обрабатывает интервал или время ожидания. Сумма событий запроса и ответа в протоколе HTTP называется жизненным циклом опроса HTTP. Он включает в себя следующие шаги:
- Для связи с сервером клиент отправляет запрос и ждет ответа.
- Сервер отправляет ответ, когда происходит событие, обновление или изменение, или достигает тайм-аута, пока не зависнет клиентский запрос.
- Сервер отправляет ответ клиенту при наличии обновления или изменения.
- Этот цикл продолжается, когда клиент отправляет новый запрос.
Ниже приведены некоторые недостатки, связанные с опросом HTTP: кэширование, тайм-ауты, накладные расходы на заголовок и задержка. Создание приложения реального времени с помощью WebSocket устраняет эти ловушки, связанные с опросом HTTP.
HTTP Stream. При опросе HTTP сервер закрывается, не отправляя ответ клиенту. Это основная причина задержки в сети, связанной с опросом HTTP. При HTTP-опросе сервер закрывает канал подключения запроса после ответа. Закрытие канала соединения означает, что клиенту придется создавать новое соединение при каждом новом запросе. HTTP Stream решает эту проблему. В HTTP Stream первоначальный запрос остается открытым даже после того, как сервер ответил на запрос клиента данными. Если оставить канал запроса открытым на неопределенный срок, сервер сможет постоянно отправлять ответы клиенту всякий раз, когда доступны новые данные, обновления или изменения. HTTP Stream сокращает задержку, доставляет обновления практически в режиме реального времени и использует ресурсы сервера. Ограничение, связанное с потоковой передачей данных через HTTP, заключается в том, что для этого требуется, чтобы клиент инициировал запрос и установил соединение, а задержка также может быть проблемой при потоковой передаче.
Server-Sent Event (SSE). Протокол событий, отправленных сервером, обеспечивает однонаправленный канал связи (обслуживание-клиент) для потоковой передачи данных в реальном времени или почти в реальном времени. SSE — это стандартизированный протокол потоковой передачи HTTP со встроенным API браузера.
Примечание: Firefox не поддерживает использование SSE в сервис-воркерах.
SSE — отличный выбор для однонаправленной передачи данных от сервера к клиенту. SSE идеально подходит для случаев использования, когда нет необходимости отправлять данные от клиента на сервер. Например, SSE особенно полезен для обработки обновлений каналов социальных сетей и информационных панелей в реальном времени.
До сих пор мы видели недостатки, связанные с традиционными методами HTTP, и то, насколько неэффективно их использовать для реализации двунаправленной связи в реальном времени. WebSocket — более подходящий вариант для реализации двунаправленного обмена данными.
Что такое WebSocket WebSocket — это протокол передачи данных, который обеспечивает двустороннюю (двустороннюю), полнодуплексную и интерактивную связь в режиме реального времени между браузером (клиентом) и сервером через единый, долгоживущий элемент управления передачей данных. Протокол TCP-соединения. С помощью WebSocket API клиент может отправлять запросы на сервер и получать ответы на основе событий без необходимости опроса сервера.
Соединение WebSocket соответствует следующему порядку:
- Запрос Handshake. Чтобы установить соединение, клиент отправляет первоначальный запрос на сервер, это называется рукопожатием WebSocket.
- Проверка запроса: после получения запроса сервер проверяет его действительность и подключается, если проверка прошла успешно.
- Связь: после успешной проверки устанавливается соединение WebSocket, и сервер и клиент могут передавать данные друг другу.
WebSocket API поддерживается большинством основных браузеров, как показано в этой таблице совместимости.
Почему разработчикам следует использовать WebSockets Из-за несоответствий, связанных с методами HTTP; не рекомендуется создавать приложение с функцией двунаправленной связи с использованием традиционных методов HTTP. WebSockets обеспечивают двунаправленные каналы связи в реальном времени с малой задержкой между клиентом и сервером. Будучи легким протоколом, он также масштабируем и поддерживает полнодуплексную (двустороннюю) связь. WebSockets поддерживаются большинством современных браузеров.
Раскрытие возможностей WebSockets с помощью Node.js и React
В соответствии с порядком подключения WebSocket, обсуждавшимся ранее, протокол WebSocket начинается с создания рукопожатия, а затем разрешает связь после подтверждения запроса путем отправки данных. Соединение между клиентом и сервером должно быть установлено посредством рукопожатия. Передача данных в реальном времени между клиентом и сервером возможна после завершения рукопожатия.
Мы продемонстрируем, как настроить соединение WebSocket с помощью Node.js и React.js. Чтобы проиллюстрировать возможности WebSockets, мы будем использовать в качестве примера приложение для совместного редактирования текста. Несколько человек могут одновременно сотрудничать и редактировать текст с помощью этого приложения, и любые изменения, внесенные одним человеком, немедленно становятся видимыми для всех остальных пользователей.
Доступ к коду
Прежде чем углубляться в особенности WebSockets, важно помнить, что в репозитории Git доступен весь код. Исходный код прототипа приложения для совместного редактирования текста, который будет использоваться в качестве нашего реального примера для практического понимания веб-сокетов, находится в корневой папке (WebSockets-Demo-main), которую можно получить по предоставленному URL-адресу (Приложение для совместного редактирования текста). Чтобы следовать инструкциям, клонируйте или загрузите репозиторий.
Запустите приложение после настройки среды.
Следующим шагом после загрузки и распаковки файла является настройка вашей среды. Используя ваш любимый редактор кода, откройте разархивированную папку. Доступны два основных каталога:
Каталог сервера: это расположение сервера Node.js WebSocket. Он отвечает за управление основной логикой текстового редактора.
Клиентский каталог: включает в себя приложение React, которое взаимодействует с сервером WebSocket. Он отвечает за функции приложения в режиме реального времени. Через него пользователь взаимодействует с приложением.
Вам необходимо выполнить несколько команд, чтобы запустить приложение для редактирования текста. Эти команды запустят сервер и клиент и установят необходимые пакеты. Вы можете открыть приложение в двух разных окнах браузера и редактировать текст одновременно после того, как сервер и клиент будут запущены и заработают. Здесь вы начнете ощущать возможности WebSockets в режиме реального времени.
Понимание Кодекса
Понимание рукопожатия WebSocket
Сервер и клиент начинают рукопожатие по протоколу WebSocket. На уровне сервера включается HTTP-сервер, а сервер WebSocket подключается через один порт. Сервер WebSocket подключается к порту HTTP после настройки сервера HTTP. С этого начинается процесс создания соединения WebSocket, и он служит началом обмена между сервером и клиентом, во многом похожего на виртуальное рукопожатие.
// Import required modules
const { WebSocket, WebSocketServer } = require('ws');
const http = require('http');
const uuidv4 = require('uuid').v4;
// Create an HTTP server and a WebSocket server
const server = http.createServer();
const wsServer = new WebSocketServer({ server });
const port = 8000;
// Start the WebSocket server
server.listen(port, () => {
console.log(`WebSocket server is running on port ${port}`);
});
Управление клиентскими подключениями
Управление клиентскими соединениями имеет важное значение в любом приложении реального времени. Каждому клиенту предоставляется индивидуальный ключ, созданный пакетом uuid, и все подключенные клиенты отслеживаются как объект в коде. Специальный ключ создается, и соединение сохраняется при получении нового запроса на подключение клиента. Это позволяет серверу эффективно управлять всеми активными в данный момент соединениями.
// Maintain active connections and users
const clients = {};
const users = {};
let editorContent = null;
let userActivity = [];
// Handle new client connections
wsServer.on('connection', function handleNewConnection(connection) {
const userId = uuidv4();
console.log('Received a new connection');
clients[userId] = connection;
console.log(`${userId} connected.`);
connection.on('message', (message) => processReceivedMessage(message, userId));
connection.on('close', () => handleClientDisconnection(userId));
});
Новые клиентские подключения обрабатываются функцией handleNewConnection
как события. Когда новый клиент создает соединение WebSocket с сервером, оно активируется.
Библиотека uuidv4
используется для создания отдельного идентификатора пользователя при каждом новом соединении.
UserId служит ключом для хранения соединения в объекте клиентов.
Новое соединение сигнализируется печатью сообщения в журнале.
Входящие сообщения от клиентов обрабатываются функцией processReceivedMessage
. Ответное сообщение передается всем подключенным клиентам после анализа сообщения, установления типа сообщения и выполнения действий в зависимости от типа сообщения (например, действий пользователя по присоединению или редактированию содержимого).
// Handle incoming messages from clients
function processReceivedMessage(message, userId) {
const dataFromClient = JSON.parse(message.toString());
const json = { type: dataFromClient.type };
if (dataFromClient.type === eventTypes.USER_EVENT) {
users[userId] = dataFromClient;
userActivity.push(`${dataFromClient.username} joined to collaborate`);
json.data = { users, userActivity };
} else if (dataFromClient.type === eventTypes.CONTENT_CHANGE) {
editorContent = dataFromClient.content;
json.data = { editorContent, userActivity };
}
sendMessageToAllClients(json);
}
Как видите, новое соединение успешно получено.
Когда пользователь вводит свое имя и нажимает «Присоединиться к документу», сообщение транслируется всем подключенным клиентам, к которым пользователь присоединился для совместной работы. Пожалуйста, смотрите скриншоты приложения ниже:
Установление рукопожатия на уровне клиента
В приведенном ниже коде пакет реагирования-использования-websocket используется на стороне клиента для запуска соединения WebSocket. Перехват useWebSocket, предоставляемый этим пакетом, позволяет функциональным компонентам React управлять соединениями WebSocket. Это способ клиента связаться с сервером для рукопожатия. С точки зрения клиента, это начальный этап создания соединения WebSocket. WebSockets очень легко понять и использовать, если мы хорошо понимаем различные типы событий: onopen
, onclose
или onmessage
.
import React, { useEffect, useState } from 'react';
import { Navbar, NavbarBrand } from 'react-bootstrap';
import useWebSocket, { ReadyState } from 'react-use-websocket';
import { Tooltip as ReactTooltip } from 'react-tooltip';
import { DefaultEditor } from 'react-simple-wysiwyg';
import Avatar from 'react-avatar';
import './App.css';
const WS_URL = 'ws://127.0.0.1:8000';
function isUserEvent(message) {
const parsedMessage = JSON.parse(message.data);
return parsedMessage.type === 'userevent';
}
function isDocumentEvent(message) {
const parsedMessage = JSON.parse(message.data);
return parsedMessage.type === 'contentchange';
}
function App() {
const [username, setUsername] = useState('');
const { sendJsonMessage, readyState } = useWebSocket(WS_URL, {
onOpen: () => {
console.log('WebSocket connection established.');
},
share: true,
filter: () => false,
retryOnError: true,
shouldReconnect: () => true
});
// Rest of the component code
}
Теперь, когда пользователь присоединяется, соединение через веб-сокет устанавливается на уровне клиента. На снимке экрана выше показано «Соединение WebSocket установлено» в журналах консоли, когда соединение открывается для клиента.
Передача сообщений в реальном времени
Соединение WebSocket может передавать сообщения по мере их получения, как только клиент и сервер установили соединение через событие установления связи WebSocket. Пользователи могут совместно работать и редактировать текст в режиме реального времени в примере приложения React. Приложение также отслеживает действия пользователя и изменения контента, передавая эти события каждому другому подключенному клиенту.
function App() {
const [username, setUsername] = useState('');
const { sendJsonMessage, readyState } = useWebSocket(WS_URL, {
onOpen: () => {
console.log('WebSocket connection established.');
},
share: true,
filter: () => false,
retryOnError: true,
shouldReconnect: () => true
});
useEffect(() => {
if (username && readyState === ReadyState.OPEN) {
sendJsonMessage({
username,
type: 'userevent'
});
}
}, [username, sendJsonMessage, readyState]);
return (
<>
<Navbar className="navbar" color="light" light>
<NavbarBrand href="/">Real-time Collaborative Text Editor</NavbarBrand>
</Navbar>
<div className="container-fluid">
{username ? <EditorSection /> : <LoginSection onLogin={setUsername} />}
</div>
</>
);
}
function EditorSection() {
return (
<div className="main-content">
<div className="document-holder">
<div className="current-users">
<Users />
</div>
<Document />
</div>
<div className="history-holder">
<History />
</div>
</div>
);
}
function Document() {
const { lastJsonMessage, sendJsonMessage } = useWebSocket(WS_URL, {
share: true,
filter: isDocumentEvent
});
let html = lastJsonMessage?.data.editorContent || '';
function handleHtmlChange(e) {
sendJsonMessage({
type: 'contentchange',
content: e.target.value
});
}
return <DefaultEditor value={html} onChange={handleHtmlChange} />;
}
На снимке экрана ниже показано, что когда клиентское соединение установлено, пользователи могут сотрудничать и редактировать текст в режиме реального времени, каждое действие будет передаваться каждому клиенту соединения.
Обработка отключений
Обработка отключений так же важна для любого приложения реального времени, как и управление соединениями. WebSocket инициирует событие закрытия, когда пользователь отключается. Согласно приведенному ниже коду, когда пользователь закрывает свой браузер или обновляет страницу, сервер может уведомить остальных пользователей об отключении этого пользователя.
// Handle disconnection of a client
function handleClientDisconnection(userId) {
console.log(`${userId} disconnected.`);
const json = { type: eventTypes.USER_EVENT };
const username = users[userId]?.username || userId;
userActivity.push(`${username} left the editor`);
json.data = { users, userActivity };
delete clients[userId];
delete users[userId];
sendMessageToAllClients(json);
}
Когда пользователь закрывает окно браузера или обновляет страницу, приложение отключит клиент и уведомит каждого подключенного пользователя, передав сообщение о том, что этот пользователь покинул редактор.
Заключение
Полнодуплексная двусторонняя связь в реальном времени является важным аспектом современной веб-разработки. WebSockets предоставляет актуальные и наиболее эффективные средства для обеспечения связи в реальном времени. В этой статье мы рассмотрели концепцию WebSockets, ее преимущества, почему они превосходят другие традиционные методы HTTP и почему разработчикам следует использовать WebSockets. Наконец, мы продемонстрировали, как интегрировать WebSockets в приложения React и Node.js.