В этой статье я распишу по шагам как сделать простое приложение использующее вебсокеты, с применением сервера Centrifugo и отправкой сообщений из PHP.

Что такое вебсокет

Вебсокет — это универсальный механизм для общения по сети двух программ.

Сервер открывает соединение на свой IP и какой-то порт TCP. Клиент подключается по этому адресу и порту, и начинает общаться с сервером. Сообщения можно отправлять и принимать в обе стороны. По окончанию работы клиент закрывает соединение.

Для чего применяется вебсокет

В веб-разработке вебсокеты применяются там, где нужно больше скорости или гибкости в сетевом взаимодействии по сравнению с классическими запросами HTTP. Низкие расходы на доставку сообщений ещё и экономят трафик.

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

Примеры:

  1. Чат
  2. Многопользовательская онлайн-игра
  3. Рассылка оповещений и уведомлений
  4. Обновление страницы новыми данными без перезагрузки
  5. Быстрая коммуникация двух сервисов

Сервер Centrifugo

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

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

Установка Centrifugo

Используем Centrifugo V4.0.1, актуальную на сегодня.

Установку рассматриваю на примере Ubuntu 20.04, у меня Windows с WSL на которой установлен Ubuntu.

Открываем страничку с релизами https://github.com/centrifugal/centrifugo/releases, выбираем актуальный релиз для своей платформы.

В случае Ubuntu это centrifugo_4.0.1_linux_amd64.tar.gz.

Копируем ссылку и скачиваем себе архив.

wget https://github.com/centrifugal/centrifugo/releases/download/v4.0.1/centrifugo_4.0.1_linux_amd64.tar.gz

Распаковываем архив.

tar -xvzf centrifugo_4.0.1_linux_amd64.tar.gz

Проверяем, что файл запускается:

./centrifugo version

Настройка Centrifugo

Создаём файл конфигурации.

./centrifugo genconfig

Будет создан файл config.json с случайным образом сгенерированными ключами.

Добавляем следующие параметры:

{
  ...,
  "admin": true,
  "allowed_origins": [
	"*"
  ],
  "allow_subscribe_for_client": true,
  "presence": true,
  "history_size": 100,
  "history_ttl": "30000000s"
}

Админка Centrifugo

В комплекте с сервером Centrifugo идёт встроенная админка.

Запускаем сервер и админка тоже запустится:

./centrifugo

По умолчанию сервер настроен на порт 8000, там же и откроется наша админка:

http://localhost:8000

Открываем админку. Для входа в админку используем логин и пароль из файла config.json.

Эта панель может пригодиться нам в будущем для отладки.

Создаём клиент вебсокета на JavaScript

Создаём файл index.html:

<html>

<head>
  <title>Centrifugo quick start</title>
</head>

<body>
  <div id="counter">-</div>
  <script src="https://unpkg.com/centrifuge@3.0.0/dist/centrifuge.js"></script>
  <script type="text/javascript">
    const container = document.getElementById('counter');

    const centrifuge = new Centrifuge("ws://localhost:8000/connection/websocket", {
      token: "YOUR_TOKEN"
    });

    centrifuge.on('connecting', function (ctx) {
      console.log(`connecting: ${ctx.code}, ${ctx.reason}`);
    }).on('connected', function (ctx) {
      console.log(`connected over ${ctx.transport}`);
    }).on('disconnected', function (ctx) {
      console.log(`disconnected: ${ctx.code}, ${ctx.reason}`);
    }).connect();

    const sub = centrifuge.newSubscription("channel");

    sub.on('publication', function (ctx) {
      container.innerHTML += '<br>' + ctx.data.value;
    }).on('subscribing', function (ctx) {
      console.log(`subscribing: ${ctx.code}, ${ctx.reason}`);
    }).on('subscribed', function (ctx) {
      console.log('subscribed', ctx);
    }).on('unsubscribed', function (ctx) {
      console.log(`unsubscribed: ${ctx.code}, ${ctx.reason}`);
    }).subscribe();
  </script>
</body>

</html>

Это веб-страница с клиентским подключением к серверу Centrifugo.

Как только с сервера придёт сообщение, оно сразу же появится на странице.

Нам понадобится веб-сервер чтобы запустить её как полноценный веб-сервис.

Далеко ходить не будем, Centrifugo и это умеет:

./centrifugo serve 3000

Теперь открываем страницу по адресу http://localhost:3000 .

Создаём токен JWT

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

Мы могли бы создать его сами, но к счастью Centrifugo может и это сделать за нас:

./centrifugo gentoken -u 123456

Здесь 123456 это ID вымышленного пользователя, для которого мы создаём токен.

Копируем токен из вывода команды, вставляем в файл index.html вместо текста YOUR_TOKEN.

Обновляем страницу. Теперь подключение должно выполниться успешно.

Отправка в вебсокет из CURL

У нас есть сервер вебсокетов, есть клиент на JS который “слушает” все сообщения и выводит их пользователю.

Для отправки сообщения на сервер нам нужно отправить HTTP-запрос на API Centrifugo.

Сделаем это через CURL:

curl --header "Content-Type: application/json" \
  --header "Authorization: apikey YOUR_API_KEY" \
  --request POST \
  --data '{"method": "publish", "params": {"channel": "channel", "data": {"value": "Hello from CURL!"}}}' \
  http://localhost:8000/api

В заголовке Authorization вместо YOUR_API_KEY нужно указать параметр api_key из config.json.

Выполнив эту команду, убеждаемся что сообщение доставлено на клиентской странице (http://localhost:3000).

Отправка в вебсокет из PHP

Создаём проект PHP.

mkdir centrifugo-php-client
cd centrifugo-php-client
composer init

Подключаем клиентский модуль для API Centrifugo.

composer require centrifugal/phpcent

Создаём файл send.php

<?php

require "./vendor/autoload.php";

if (count($argv) < 2) {
    die('Нужно указать сообщение в качестве параметра');
}

$message = $argv[1];

$client = new \phpcent\Client("http://localhost:8000/api");
$client->setApiKey("YOUR_API_KEY");
$result = $client->publish("channel", ["value" => $message]);
var_dump($result);

Вместо YOUR_API_KEY указываем ключ API из параметра api_key в файле config.json.

Отправляем сообщение через PHP:

php send.php "Hello from PHP!"

Открываем вкладку клиентского приложения и убеждаемся что сообщение пришло.

Готово. Теперь на основе этого кода мы сможем создать своё собственное приложение использующее вебсокеты.

Отправка в вебсокет из клиента

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

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

Чтобы эта возможность заработала, нужно включить опцию allow_publish_for_subscriber в файле config.json.

Бонус. Прослушка сообщений на PHP

Для отладки сетевых взаимодействий я предпочитаю открывать несколько окон консоли bash: в одном окне отправляешь сообщение, в соседнем видишь что оно доставлено.

К сожалению, используя библиотеку centrifugal/phpcent мы можем только отправлять сообщения в Centrifugo.

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

Но мы можем вытащить историю сообщений по API.

Файл history.php:

<?php

require "./vendor/autoload.php";

$client = new \phpcent\Client("http://localhost:8000/api");
$client->setApiKey("YOUR_API_KEY");
$history = $client->history("channel", -1);
$messages = $history->result->publications;

foreach ($messages as $message) {
    echo "{$message->data->value}\n";
}

Заменяем YOUR_API_KEY на ключ API из параметра api_key в файле config.json.

Теперь запускаем этот файл раз в секунду и получаем прослушку сообщений, не через вебсокеты а через API.

watch -n 1 php history.php