Общение в реальном времени с помощью Socket.IO и Node.js

Репозиторий Github

Socket.IO обеспечивает обмен данными в реальном времени на основе событий в обоих направлениях (на стороне клиента и сервера). Socket.IO построен на базе WebSockets API и Node.js.

Приложение реального времени (RTA) — это приложение, которое функционирует в течение периода, воспринимаемого пользователем как немедленное. Примерами приложений реального времени являются:

  • система мгновенного обмена сообщениями — приложения для чатов, такие как WhatsApp, Facebook Messenger и т.д,
  • push-уведомления,
  • приложения типа Google docs, позволяющие нескольким людям одновременно обновлять один и тот же документ.

Написать приложение реального времени без WebSockets, используя HTTP-запросы, традиционно очень сложно. Он включает в себя опрос сервера на предмет изменений и по своей конструкции является очень медленным и требует больше ресурсов.

Сокеты — это более простое и быстрое решение, на котором построено большинство систем реального времени, обеспечивающее двунаправленный канал связи между клиентом и сервером. Это означает, что при возникновении какого-либо события сервер может посылать сообщения клиентам, чтобы клиент немедленно получал уведомление, и наоборот.

Socket.IO довольно популярен, его используют Microsoft Office, Yammer, Trello…

Установка и код сервера

Несмотря на то, что этот учебник представлен в серии, данный конкретный учебник является самостоятельным, поэтому не нужно включать предыдущий код. Давайте начнем с серверного кода и установим все необходимые библиотеки.

npm install --save socket.io express
npm install --save-dev @types/socket.io
Вход в полноэкранный режим Выход из полноэкранного режима

Начальный код сервера:

import express from 'express';
import { Server } from 'socket.io';

const app = express();

const server = app.listen(3000, () => {
  console.log('Application started on port 3000!');
});

const socketIo = new Server(server, {
  cors: {
    origin: '*', // Allow any origin for testing purposes. This should be changed on production.
  },
});

socketIo.on('connection', (socket) => {
  console.log('New connection created');

  // Get the auth token provided on handshake.
  const token = socket.handshake.auth.token;
  console.log('Auth token', token);

  try {
    // Verify the token here and get user info from JWT token.
  } catch (error) {
    socket.disconnect(true);
  }

  // A client is disconnected.
  socket.on('disconnect', () => {
    console.log('A user disconnected');
  });

  // Read message recieved from client.
  socket.on('message_from_client', (data) => {
    console.log('message_from_client: ', data);
  });

  // Send a message to the connected client 5 seconds after the connection is created.
  setTimeout(() => {
    socket.emit('message_from_server', `Message: ${Math.random()}`);
  }, 5_000);
});
Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше фрагменте кода мы создали сервер Express на порту 3000, а затем создали сервер Socket.IO. socketIo.on('connection', (socket) вызывается, когда инициируется новое соединение со стороны клиента. Это называется рукопожатием, и первым шагом после этого является получение токена аутентификации от клиента и его проверка. Если JWT не соответствует действительности, мы отключим клиента, и он не будет получать никаких событий от сервера, а если токен действителен, мы сможем получить данные пользователя из JWT.

Отправка данных с клиента на сервер и наоборот довольно проста.

  • Для чтения данных мы используем socket.on либо с клиента на сервер, либо с сервера на клиент.
  • Для отправки данных мы используем socket.emit либо от клиента к серверу, либо от сервера к клиенту. В приведенном ниже коде мы читаем данные со стороны клиента и слушаем событие message_from_client.
socket.on('message_from_client', (data) => {
  console.log('message_from_client: ', data);
});
Вход в полноэкранный режим Выход из полноэкранного режима

Всякий раз, когда клиент выдает событие socketIo.emit('message_from_client', 'Отправлено событие от клиента!'); сервер будет считывать данные в реальном времени.

В приведенном ниже коде мы видим, как можно отправить данные с сервера на клиент с именем события message_from_server. Клиент, прослушивающий событие message_from_server, будет считывать данные в реальном времени.

setTimeout(() => {
  socket.emit('message_from_server', `Message: ${Math.random()}`);
}, 5_000);
Вход в полноэкранный режим Выход из полноэкранного режима

Код клиента

Давайте создадим простой html-файл со следующим кодом. Мы установим связь клиента с сервером по нажатию кнопки.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Socket tutorial</title>
  </head>
  <body>
    <button onclick="establishConnection()">Join</button>

    <script
      src="https://cdn.socket.io/3.1.3/socket.io.min.js"
      integrity="sha384-cPwlPLvBTa3sKAgddT6krw0cJat7egBga3DJepJyrLl4Q9/5WLra3rrnMcyTyOnh"
      crossorigin="anonymous"
    ></script>
    <script>
      let isConnectionEstablished = false;

      function establishConnection() {
        if (isConnectionEstablished) {
          return;
        }

        isConnectionEstablished = true;

        const socketIo = io('http://localhost:3000', {
          auth: {
            token: 'json-web-token',
          },
        });

        socketIo.on('connect', function () {
          console.log('Made socket connection', socketIo.id);
        });

        socketIo.on('message_from_server', function (data) {
          console.log('message_from_server data: ', data);
        });

        socketIo.on('disconnect', function () {
          console.log('disconnect');
        });

        // Send a message to the server 3 seconds after initial connection.
        setTimeout(function () {
          socketIo.emit('message_from_client', 'Sent an event from the client!');
        }, 3000);

        socketIo.on('connect_error', function (err) {
          console.log('connection errror', err);
        });

      }
    </script>
  </body>
</html>
Войти в полноэкранный режим Выйти из полноэкранного режима

Важно отметить, что мы предоставили клиентскую библиотеку Socket.IO вместе со скриптом из CDN.

<script
  src="https://cdn.socket.io/3.1.3/socket.io.min.js"
  integrity="sha384-cPwlPLvBTa3sKAgddT6krw0cJat7egBga3DJepJyrLl4Q9/5WLra3rrnMcyTyOnh"
  crossorigin="anonymous"
></script>
Вход в полноэкранный режим Выход из полноэкранного режима

Создание канала связи путем отправки токена, который необходим для проверки пользователя.

const socketIo = io('http://localhost:3000', {
  auth: {
    token: 'json-web-token',
  },
});
Вход в полноэкранный режим Выход из полноэкранного режима

Код для чтения сообщений с сервера по событию message_from_server:

socketIo.on('message_from_server', function (data) {
  console.log('message_from_server data: ', data);
});
Вход в полноэкранный режим Выход из полноэкранного режима

Код для отправки данных с клиента на сервер:

setTimeout(function () {
  socketIo.emit('message_from_client', 'Sent an event from the client!');
}, 3000);
Войти в полноэкранный режим Выход из полноэкранного режима

Запуск приложения

На стороне сервера добавьте скрипт для запуска сервера в файл package.json:

"scripts": {
  "start": "ts-node src/server.ts"
},
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь запустим приложение Node.js:

npm run start
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь мы можем открыть файл index.html в любом браузере. Вы должны увидеть кнопку ‘Join’, как показано на изображении Image 1 - client.

Откройте консоль в браузере и после этого нажмите кнопку ‘Join’, вы должны увидеть, что сервер передает данные клиенту, как показано на Изображение 2 - консоль клиента.

Если вы посмотрите на терминал на сервере, то увидите, что клиент передает данные на сервер, как показано на изображении Изображение 3 - терминал сервера.

Комнаты

Время от времени необходимо отделить определенных пользователей, чтобы мы могли отправлять события только определенным пользователям. Одним из хороших примеров использования комнат является чат. Чат может быть создан для одного или нескольких человек, и только пользователи, находящиеся в определенной комнате, могут получать определенные события.

Обновлен код сервера с комнатами.

socketIo.on('connection', (socket) => {
  console.log('New connection created');

  const token = socket.handshake.auth.token;
  console.log('Auth token', token);

  try {
    // Verify the token here and get user info from JWT token.
  } catch (error) {
    socket.disconnect(true);
  }

  // A client is disconnected.
  socket.on('disconnect', () => {
    console.log('A user disconnected');
  });

  // Read message recieved from client.
  socket.on('message_from_client', (data) => {
    console.log('message_from_client: ', data);
  });

  // Send a message to the connected client 5 seconds after the connection is created.
  setTimeout(() => {
    socket.emit('message_from_server', `Message: ${Math.random()}`);
  }, 5_000);

  /**
    * New code
    */
  // Get the room number from the client.
  const roomNumber: string = socket.handshake.query.roomNumber as string;
  // Join room for specific users.
  const room = `room-userId-${roomNumber}`;
  socket.join(room);

  // Emit to room by room number.
  setTimeout(() => {
    socketIo.to(room).emit('room-userId', `You are in room number: ${roomNumber}`);
  }, 2_000);
});
Вход в полноэкранный режим Выход из полноэкранного режима

Идея заключается в том, чтобы получить номер комнаты от клиента и присоединить конкретных пользователей к определенным комнатам. После того как пользователь присоединится к определенной комнате, он будет получать события всякий раз, когда мы будем отправлять данные в определенные комнаты.

// Get the room number from the client.
const roomNumber: string = socket.handshake.query.roomNumber as string;
// Join room for specific users.
const room = `room-userId-${roomNumber}`;
socket.join(room);

// Emit to room by room number.
setTimeout(() => {
  socketIo.to(room).emit('room-userId', `You are in room number: ${roomNumber}`);
}, 2_000);
Вход в полноэкранный режим Выход из полноэкранного режима

На стороне клиента добавим ввод, где пользователи смогут ввести номер комнаты, и отправим номер комнаты на сервер после того, как пользователь нажмет кнопку присоединиться.

<!-- Add text input field next to 'Join' button -->
<input type="text" placeholder="Room number" id="roomId" />
<button onclick="functionToExecute()">Join</button>
Вход в полноэкранный режим Выход из полноэкранного режима
// Update connection for Socket.
const socketIo = io('http://localhost:3000', {
  auth: {
    token: 'json-web-token',
  },
  query: {
    roomNumber: document.getElementById('roomId').value, // <- new code
  },
});
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь давайте откроем две вкладки клиентского приложения и присоединимся к одной комнате. Люди из одной комнаты всегда будут видеть, когда кто-то присоединяется к комнате, как показано на изображении Иллюстрация 4 - присоединение комнат.

Подведение итогов

В этом уроке мы узнали, что такое вебсокеты и каковы преимущества использования вебсокетов вместо HTTP для коммуникации в реальном времени, а также узнали, что Socket.IO является наиболее популярным вариантом для использования вебсокетов в Node.js. Socket.IO широко используется самыми популярными компаниями, такими как Microsoft, Trello и т.д. Мы узнали, как создать Express-сервер с использованием Socket.IO и как использовать его на стороне клиента. Мы узнали, как отправлять JWT-токены на Socket.IO handshake и как отправлять любые дополнительные данные во время создания начальной связи. Мы также увидели, в чем преимущества и для чего используются комнаты в Socket.IO.

Оцените статью
Procodings.ru
Добавить комментарий