Привет 👋, надеюсь, у тебя все хорошо,

Давно мы не виделись!

Много времени


Сегодня я хочу поговорить с вами немного о socket.io

Это очень приятная маленькая библиотека, которая позволяет нам легко управлять нашими сокетами в nodejs.

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

Это было очень просто, так что посмотрим, что получится!


Давайте немного организуем все это по главам:


Оглавление




Творчество

Итак, если позволите, начнем с самого начала, создания проекта!

Начато
У нас все еще есть некоторые ожидания в том смысле, что мы не собираемся «просто» копировать/вставлять код из документации.

Мы собираемся сделать небольшой сервер с:


Давай, наконец, приступим!

Поскольку эта часть довольно проста, мы быстро пройдемся по первым командам.



Создаем новую папку для нашего проекта и входим в нее :

mkdir socketio-numberOfOnlineUser-ts
cd socketio-numberOfOnlineUser-ts
Войти в полноэкранный режим

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




Мы запускаем наш новый проект узла :

npm init -y
Войти в полноэкранный режим

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

(с -y, потому что мы не хотим терять время на кли, -y одобряет все по умолчанию)




Добавляем наши красивые зависимости! :

npm i express rxjs socket.io
npm i -D @types/express @types/socket.io nodemon ts-node tslint typescript
Войти в полноэкранный режим

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




Подготовьте перезагрузку в реальном времени с помощью скрипта в package.json.

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

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

создать nodemon.json файл ниже вашего package.json с:

touch nodemon.json
Войти в полноэкранный режим

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

Вот так ⬇️
конфигурация нодмона

И поместите это на него (он будет использовать этот конфиг для команды nodemon, ваш нпм запустить разработчик):

{
  "ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"],
  "watch": ["src"],
  "exec": "npm start",
  "ext": "ts"
}
Войти в полноэкранный режим

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




Запустите файл tsconfig:

npx tsc --init --rootDir src --outDir dist --module commonjs --lib es2015  --sourceMap true --target es6 --esModuleInterop true
Войти в полноэкранный режим

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



Создадим наши папки:

mkdir src src/models src/services
Войти в полноэкранный режим

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

Вы получите это ⬇️
Папки проекта



Создадим наши файлы:

touch src/index.ts src/index.html src/models/index.ts src/models/Socket.ts src/services/index.ts src/services/Socket.ts
Войти в полноэкранный режим

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

Обычно на этом этапе ваша иерархия должна выглядеть так ⬇️
Папки и файлы проекта
Для стадии создания проекта мы хороши!
Следующий шаг !




Код

Наконец-то код! Веселая часть!

Веселая часть
Итак, давайте откроем vscode и начнем с index.ts файл.

В этой части мы запустим базовый сервер узла под экспрессом, вы знаете, здесь нет серета, так что это выглядит так:

import express from "express"; // We import express as listener, you know.
import * as http from "http"; // We import http for init the server
// Anyway, if we don't do it, express does it secretly

const app =  express(); // Init express
const server = http.createServer(app); // Init server avec use express as listener
const port =  3000;

app.get("/", (req, res) => {
res.sendFile(__dirname +  "/index.html"); // We Will use the index.html file to see our work
});

server.listen(port, () => {
return console.log(`server is listening on ${port}`); // No need to comment that, no?
});
Войти в полноэкранный режим

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


На данном этапе у нас просто работает сервер nodejs и возвращает пустую страницу.

Продолжим.


Давайте подготовим наш файл index.html, мы просто хотим видеть количество онлайн-пользователей в режиме реального времени.

Давайте поместим это на него:

<div>
    <p>Number of users : <span id="numberOfUsers"></span></p>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>

var socket = io("localhost:3000");
var numberOfUsers = document.getElementById("numberOfUsers");

socket.on("dataChange", function (data) {
  numberOfUsers.textContent = data.numberOfUser;
});
</script>
Войти в полноэкранный режим

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

Ах, наконец, наше первое упоминание о SocketIo, но что происходит?

Здесь мы инициализируем экземпляр SocketIo (с io(‘внутренний адрес’)) на стороне клиента этот экземпляр попытается подключиться к сокету, «обычно» доступному по вашему адресу localhost: 3000, после подключения этот экземпляр будет ждать отправки события «dataChange» с вашего сервера. (с методом socket.on(»)) .

Однако на вашем сервере пока нет ничего по этой теме, поэтому при проверке страницы вы просто получите сообщение об ошибке.

Но продолжим.


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

Иди на источник/модели папку и поместите это в наш файл Socket.ts

export interface SocketData {
  numberOfUser?: number;
}
Войти в полноэкранный режим

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

О, но рядом с нашим файлом Socket.ts у нас есть файл index.ts, давайте позаботимся о нем, если это «бочка», полезная для эффективной организации всего нашего будущего импорта/экспорта.

Итак, используйте его и поместите в него это:

export * from "./Socket";
Войти в полноэкранный режим

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


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

Теперь давайте позаботимся о нашем экземпляре Socket! Окончательно !

Итак, давайте позаботимся о источник/услуги папка, внутри у нас наш бочонок index.ts (теперь вы знаете, что с этим делать, верно? экспортировать blablabla)

Перейдем к самому важному файлу, src/услуги/Socket.ts

Наконец, мы будем использовать RxJS для создания переменной, которая будет «наблюдаемой».

То есть функция (обратный вызов), которая будет выполняться при каждом изменении данных, содержащихся в этой переменной.

Таким образом, мы можем заставить наш сокет реагировать легко и динамично!

У нас также будет функция, которая увеличивает или уменьшает значение число пользователей значение, чтобы клиент мог видеть изменения, когда посетитель входит/выходит из системы.

Вкратце вот состояние нашего знаменитого файла ⬇️

import * as socketio from "socket.io";
import * as http from "http"; // Just for typing, we like typing !
import { BehaviorSubject } from "rxjs";

import { SocketData } from "../models/Socket"; // Here our model, cause we like typing things !

export class SocketService {
  private store: BehaviorSubject<SocketData> = new BehaviorSubject<SocketData>({
    numberOfUser: 0,
  }); // Here's our "Observable" data.
  private io!: socketio.Server; // Our Socket instance

  constructor(server: http.Server) {
    this.io = new socketio.Server(server, { cors: { origin: "*" } }); // We init our Socket instance with the server as parameter
  }

  public listenStore() {
    this.store.subscribe((store) => {
      // subscribe is called at each store value change !
      this.io.emit("dataChange", store); // So at each value change, we emit this value to the client with the event name "dataChange"
    });
  }

  public listenUserActivity() {
    this.io.on("connection", (client) => {
      const storeData = this.store.getValue(); // we get the actual value of our "Observable"
      // When a user do a connection at our socket, the "connection" event is called. It's a default event from socket.io.
      this.store.next({ numberOfUser: storeData.numberOfUser + 1 }); // so we change the data of the store with the function "next"
      client.once("disconnect", () => {
        const storeData = this.store.getValue(); // we get the actual value of our "Observable"
        // When a user do a disconnection, the "disconnect" event is called. It's a default event from socket.io.
        if (storeData.numberOfUser !== 0) {
          // A check just by security.
          this.store.next({ numberOfUser: storeData.numberOfUser - 1 }); // You know what next do now.
        }
      });
    });
  }
}

Войти в полноэкранный режим

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

И что это за файл socket.ts!


Теперь, когда у нас есть готовая служба Socket с нужной нам логикой, нам нужно сообщить нашему серверу, что она существует.

Для этого мы отредактируем файл src/index.ts следующим образом.

import express from "express";
import * as http from "http";
import { SocketService } from "./services"; // We import our SocketService, Thanks to the index.ts file, we don't need to specify where exactly the Socket.ts file is located.

const app = express();
const server = http.createServer(app);
const port = 3000;

const socketService = new SocketService(server); // We instantiate our SocketService

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

socketService.listenStore(); // We ask to our service to listen the change of the "Observable" data and emit the change to our socket by the "dataChange" event.

socketService.listenUserActivity(); // We listen the activity of the socket and change the state of the "Observable" data when a desired event is triggered ("connection" or "disconnect")

server.listen(port, () => {
  return console.log(`server is listening on ${port}`);
});

Войти в полноэкранный режим

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

Итак, если мы вернемся на сторону клиента, мы сможем увидеть всю нашу работу!

Результат сокета

Когда мы загружаем страницу, мы хотим увидеть число пользователей изменение значения, в том числе, когда мы покинули страницу.

Это работает !

Миссия выполнена

Таким образом, вы можете начать с того, чтобы усложнить свою логику!



Дополнительный

Но что ты говоришь? Ваш клиент не на стороне узла? Вы используете переднюю структуру, такую ​​как Vuejs/Angular или Next.

Нет проблем, просто используйте socket.io-клиент пакет на лицевую сторону и делаем вот так ⬇️:

import { io, Socket } from "socket.io-client";
import { Observable, BehaviorSubject } from "rxjs";

export interface SocketData {
  numberOfUser?: number;
}

export class SocketService {
  private socket!: Socket;

  private store: BehaviorSubject<SocketData> = new BehaviorSubject<SocketData>({
    numberOfUser: 0,
  });
  private store$: Observable<SocketData> = this.store.asObservable();

  constructor() {
    this.socket = io("YOUR_BACKEND_URL");
    this.socket.on("dataChange", (data) => {
      this.emit(data); // At each "dataChange" event we modify our "Observable" data.
    });
  }

  public emit(store: SocketData): void {
    this.store.next(store);
  }

  public listen(): Observable<SocketData> {
    return this.store$; // You will be able to use this.socketService.listen().subscribe(), it's the same logic as we see above !
  }
}
Войти в полноэкранный режим

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

Я думаю, что мы видели основную часть, так что мы можем остановиться здесь!

В следующем посте мы увидим, как это развернуть на собственном VPS под apache2, вот увидите, это просто!

Хорошо, пока !

Увидимся !



Репо Github