Первые шаги в мир веба в реальном времени
Доброго времени суток. Давно хотелось написать про что-то больше, нежели чем WP. Заметил, что чем больше пытаешься прогрессировать, как разработчик, тем тривиальней кажется то, о чем хочешь написать. Но да ладно, возможно кому-то будет довольно полезен мой опыт. Цикл заметок будет ориентирован в первую очередь на тех, кто только начинает собирать свои первые реалтайм веб-приложения.
Итак, стоит задача синхронизации того, что видит пользователь и того, что есть в бд. Ранее для подобных задач использовал сервис Pusher, но, в последнее время, предпочитаю использовать Centrifuge. Предвосхищу вопрос о том, чем лучше это обычной связки redis/socket.io/node.js. Из коробки приватные каналы, простая интеграция, масштабирование, api, история сообщения в канале, события отписки и подписки пользователей на канал и много другое, что позволяет крайне быстро построить нужный прототип приложения, без раздувания стека технологий. Это работает для меня, у каждого свой путь. Кстати, язык на бекенде — php, и, соответственно, на фронтенде — js.
Что из этого получилось, некоторые нюансы — можете посмотреть ниже.
Собираем простенький прототип
В качестве первого примера возьмем следующую задачу. На экране будет график, который одинаков у всех пользователей и обновляется по мере поступления данных.
Для простоты брокер на данном этапе будет работать в insecure mode:
- Отключение проверки токена и временных меток;
- Разрешение анонимного доступа ко всем каналам;
- Клиентам разрешено публиковать сообщения во все каналы;
- Убрана проверка соединения.
Конечно, в боевом режиме этого делать не следует, но для наших целей и ради понимания концепции — подойдет.
Дебют
Для начала необходимо развернуть центрифугу на сервере. Делается это в несколько простейших шагов:
- Выбираем необходимый исполняемый файл под свою платформу в репозитории;
- В качестве транспорта предпочитаю использовать Redis. Но, если хочется, то можно и по http.
- Пишем небольшой конфиг для нашего проекта;
- Стартуем центрифугу на 8002 порту и загоняем все это дело под nginx.
Миттельшпиль
Центрифуга работает, готова к подсоединению клиентов и отправки им сообщений. Теперь дело за бекендом.
Генерируем composer.json, делаем composer require predis/predis, пишем пару классов. Собственно сам модуль отправки сообщений в брокер и транспорт, через редис. Т.к. центрифуга на данном этапе работает в insecure mode, токены генерировать нет нужды и задача еще более упрощается.
Если кому интересно — конечный вариант наброска и демо. В демо точки уничтожаются каждые 240 секунд, просто можно пощелкать кнопку на добавление новых в историю и обновить страницу. В реальном мире, конечно, данные берутся с api, в следующих заметках мы вернемся к этому.
Как итог, теперь мы можем отправлять с бекенда пользователям сообщения. Примерно следующим образом:
...
require __DIR__.'/vendor/autoload.php';
use Push\Push;
use RedisCommunucate\Communicate;
use Predis\Client;
$redisClient = new Communicate(new Client());
$push = new Push('development', 'secret', $redisClient);
//Точку в канал "chart"
$push->publish('chart', ['point' => rand(0,10)]);
...
...
//Отправляем в центрифугу командой "publish"
public function publish($channel, $data = [])
{
return $this->send("publish", ["channel" => $channel, "data" => $data]);
}
public function send($method, $params = [])
{
if (empty($params)) {
$params = new \StdClass();
}
$data = json_encode(["method" => $method, "params" => $params]);
return
$this->communicate
->communicate(
$this->projectKey,
["data" => $data]
);
}
...
...
//Через redis rpush
public function communicate($projectId, $data)
{
$toSend = array(
"project" => $projectId
);
$data['data'] = json_decode($data['data']);
$toSend = array_merge($toSend, $data);
$toSend['data'] = array($toSend['data']);
$this->redisClient->rpush("centrifugo.api", json_encode($toSend));
}
...
И на этом первую часть задачи можно считать выполненной. Теперь дело за фронтендом.
Эндшпиль
В качестве библиотеки для графика возьмем c3.js. Подключаем c3, sockjs, centrifuge.js.
...
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.css" rel="stylesheet" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/sockjs/1.0/sockjs.min.js" type="text/javascript"></script>
<script src="https://rawgit.com/centrifugal/centrifuge-js/master/centrifuge.js" type="text/javascript"></script>
...
Соединяемся с центрифугой, ловим точки. И сделаем кнопку, чтобы дергать сервер на генерацию новых точек.
...
var chart;
// Конфигурируем.
var centrifuge = new Centrifuge({
url: 'http://socket.logistics.app/connection',
project: 'development',
insecure: true
});
// Когда центрифуга подключилась,
centrifuge.on('connect', function() {
// просто подписываемся на канал,
var subscription = centrifuge.subscribe('chart', function(message) {
// ловим точки и добавляем в график,
chart.flow({
columns: [
['sample', message.data.point]
],
duration: 100
});
});
// не забывая взять предыдущие. В реальном мире - берем с api.
subscription.on('ready', function() {
subscription.history(function(message) {
var data = message.data.map(function(point){
return point.data.point
});
data.reverse();
data.unshift('sample');
chart = c3.generate({
bindto: '#chart',
data: {
columns: [
data
]
}
});
});
});
});
// Запускаем коннект
centrifuge.connect();
// Дергаем сервер, чтобы отправлял в центрифугу сообщения через редис
$('.random').click(function(){
$.get('/random.php');
});
...
Задача выполнена, открываем страницу с демо на разных устройствах или просто в разных браузерах. Когда появляется новая точка — график обновляется. Да, на данном этапе это все работает крайне небезопасно. Но для понимания концепции — в самый раз. Из известных проблем, решение которых я бы хотел описать в следующих заметках:
- Если между получением хистори и подпиской на канал придут новые точки, то они потеряются.
- Сортировка точек. Если они будут приходить крайне часто, то будут добавляться в неправильном порядке.
- Мы рассмотрели только добавление данных в конец. Но есть еще списочные данные. И, допустим, мы используем datatables для показа табличек. И данные обновляются, приходят с центрифуги, но мы находимся на третьей странице таблицы с фильтрами/сортировками. И вопрос в том, как синхронизировать это.
Вообщем я буду рад, если это будет кому-нибудь интересно и вдвойне рад, если это кому-нибудь поможет. Думаю, что каждому хочется в его проектах, когда один пользователь производит некое действие, все остальные могли сразу увидеть обновленные данные. Спасибо за ваше внимание, до скорой встречи.