Продолжаю серию статей про Jii Framework. Сегодня настал момент релиза комета, о котором я и расскажу в этой статье.
Jii-comet — это масштабируемый, готовый к высоким нагрузкам и плохому интернету транспорт, реализующий постоянную связь между клиентом и сервером для мгновенного обмена данными.
Jii-comet предоставляет набор компонентов и классов, которые упрощают обмен сообщениями между каналами, подписки на них, обмена данными между серверами и так далее. Сам модуль не умеет доставлять сообщения на клиент и обратно, но в нем заложена абстракция, чтобы это можно было делать любой из существующих популярных библиотек (например, socket.io, sockjs), а так же чтобы это было надежно и масштабируемо.
Для тех, кто в первый раз слышит об этом фреймворке, рекомендую прочитать предыдущие статьи или посетить сайт. Если коротко, то
Для начала немного справки о том, что такое комет (wiki):
Клиент:
Сервер:
Комет устанавливается как компонент приложения, на сервере и клиенте соответственно. На сервере комет открывает и «слушает» данные из указанного порта, а клиент при инициализации приложения подключается к этому порту и ждет информацию.
Рассмотрим пример приложения, где клиент подписывается на сервере на канал test и отправляет в него сообщение.
Сервер:
Клиент:
Далее делально рассмотрим серверные и клиентские компоненты.
Jii-comet предоставляет два класса для создания сервера:
Такое разделение необходимо для гибкого масштабирования приложения. Например, вы можете создать несколько процессов с компонентом Jii.comet.server.Server, которые будут только поддерживать соединение с клиентами, но не обрабатывать бизнес логику. И создать несколько процессов Jii.comet.server.HubServer, которые будут выполнять тяжелые вычисления. Это позволит сделать комет-канал более отзывчивым: если бы все операции были бы на одном сервере, то сложные операции затормаживали бы процесс, и все последующие запросы (даже легкие) были бы с задержкой.
Если у вас нет больших нагрузок или вы не знаете, что выбрать, выбирайте компонент Jii.comet.server.Server, так как он содержит в себе весь функционал.
Пример конфигурации для сервера выглядит так:
Полный список свойств, методов и событий представлен ниже.
message — Событие запускается при любом входящем сообщении в хаб. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.server.MessageEvent с параметром message (string).
Наследует вышеперечисленные свойства, методы и события и добавляет следующие:
Содержит информацию о соединении с клиентом
На клиенте немного проще: есть один компонент Jii.comet.client.Client, который подключается к одному из серверов и обменивается данными.
Полный список свойств, методов и событий представлен ниже.
Плагины позволяют расширять возможности комет клиента. По-умолчанию, установлен плагин для автоматического восстановления соединения Jii.comet.client.plugin.AutoReconnect. Плагин очень прост в создании и подключении. Интерфейс плагина Jii.comet.client.plugin.PluginInterface не требует имплементации методов, а лишь предоставляет доступ к комет-клиенту через параметр comet, через который можно подписываться на события комета. Устанавливается и настраивается плагин через конфигурацию, путем добавления его в секцию plugins:
С первого взгляда, jii-comet — это обертка над библиотекой комет-транспорта (sockjs, socket.io — на выбор), однако это не так. От библиотеки транспорта берется только функционал доставки сообщения на клиент и обратно. Реализация каналов, подписок, масштабирования и дополнительных фишек сделано именно в jii-comet. Архитектурно «под капотом» jii-comet разбит на следующие составляющие:
Сервер:
Клиент:
Все эти абстракции могут настраиваться и переопределяться через конфигурацию приложения и контекста.
В третьем пункте запрос из очереди мог вытянуть любой серверный процесс с listenActions = true. Работает это по принципу «кто успел» и, как правило, успевать будет наименее нагруженный сервер. Таким образом, производится балансировка вызовов действий между серверами и их процессами.
Jii-comet имеет абстракцию, позволяющую использовать любую библиотеку в качестве внутреннего канала связи между клиентом и сервером. На данный момент сделан адаптер для библиотеки sockjs, в будущем появится и для socket.io.
В конфигурации транспорт объявляется в секции transport. По-умолчанию, используется класс Jii.comet.server.transport.Sockjs на севере и Jii.comet.client.transport.Sockjs на клиенте.
Транспорт можно конфигурировать и переопределять через конфигурацию:
Сайт фреймворка — jiiframework.ru
GitHub — github.com/jiisoft
Пожелания и предложения отправляйте на affka@affka.ru
Jii-comet — это масштабируемый, готовый к высоким нагрузкам и плохому интернету транспорт, реализующий постоянную связь между клиентом и сервером для мгновенного обмена данными.
Jii-comet предоставляет набор компонентов и классов, которые упрощают обмен сообщениями между каналами, подписки на них, обмена данными между серверами и так далее. Сам модуль не умеет доставлять сообщения на клиент и обратно, но в нем заложена абстракция, чтобы это можно было делать любой из существующих популярных библиотек (например, socket.io, sockjs), а так же чтобы это было надежно и масштабируемо.
Для тех, кто в первый раз слышит об этом фреймворке, рекомендую прочитать предыдущие статьи или посетить сайт. Если коротко, то
Jii — это фреймфорк, архитектура и API которого базируется на PHP фреймворке Yii 2.0, взяв из него лучшие стороны и сохраняя преимущества JavaScript.
Обзор
Для начала немного справки о том, что такое комет (wiki):
Comet (в веб-разработке) — любая модель работы веб-приложения, при которой постоянное HTTP-соединение позволяет веб-серверу отправлять (push) данные браузеру без дополнительного запроса со стороны браузера.
Возможности Jii-comet
Клиент:
- Отправка и прием сообщений из каналов
- Подписка на каналы
- Вызов действий на сервере
- Балансировка (случайный выбор сервера)
- Возможность смены транспорта сообщений
Сервер:
- Отправка и прием сообщений из каналов
- Подписка на сообщения из канала
- Подписывание соединения на канал
- Отправка сообщений в соединение
- Возможность смены транспорта сообщений, хаба и очереди
- Масштабирование на несколько процессов и серверов
- Режим listen-only (прослушивание каналов)
- Запуск действий из очереди
Установка
Комет устанавливается как компонент приложения, на сервере и клиенте соответственно. На сервере комет открывает и «слушает» данные из указанного порта, а клиент при инициализации приложения подключается к этому порту и ждет информацию.
Рассмотрим пример приложения, где клиент подписывается на сервере на канал test и отправляет в него сообщение.
Сервер:
var Jii = require('jii');
require('jii-comet');
require('jii-workers')
.application('comet', Jii.mergeConfigs(
{
application: {
basePath: __dirname,
components: {
comet: {
className: 'Jii.comet.server.Server',
port: 4401,
/**
*
* @param {Jii.comet.server.ConnectionEvent} event
*/
'on addConnection': function(event) {
Jii.app.comet.subscribe(event.connection.id, 'test');
}
}
}
}
},
custom.comet || {}
));
Клиент:
require('jii/deps');
require('jii-comet/sockjs');
Jii.createWebApplication({
application: {
basePath: location.href,
components: {
comet: {
className: 'Jii.comet.client.Client',
serverUrl: 'http://localhost:4401/comet',
/**
*
* @param {Jii.base.Event} event
*/
'on open': function(event) {
Jii.app.comet.send('test', {message: 'Hello World'});
},
/**
*
* @param {Jii.comet.ChannelEvent} event
*/
'on channel:test': function(event) {
console.log('Income message: ' + event.params.message);
}
}
}
}
}).start();
Далее делально рассмотрим серверные и клиентские компоненты.
Комет-сервер
Jii-comet предоставляет два класса для создания сервера:
- Jii.comet.server.HubServer — сервер, настроенный только для прослушивания сообщений от соединений, при этом самих подключений у него нет, но он может отправить данные в подключение, находящееся в соседнем процессе. Т.е. в такой сервер напрямую (на порт) невозможно подключиться, однако сообщение можно отправить через другие процессы (к которым подключены клиенты).
- Jii.comet.server.Server — полноценный сервер, унаследован он предыдущего. Хранит и поддерживает прямое соединение с клиентами, может обмениваться данными с клиентом.
Такое разделение необходимо для гибкого масштабирования приложения. Например, вы можете создать несколько процессов с компонентом Jii.comet.server.Server, которые будут только поддерживать соединение с клиентами, но не обрабатывать бизнес логику. И создать несколько процессов Jii.comet.server.HubServer, которые будут выполнять тяжелые вычисления. Это позволит сделать комет-канал более отзывчивым: если бы все операции были бы на одном сервере, то сложные операции затормаживали бы процесс, и все последующие запросы (даже легкие) были бы с задержкой.
Если у вас нет больших нагрузок или вы не знаете, что выбрать, выбирайте компонент Jii.comet.server.Server, так как он содержит в себе весь функционал.
Пример конфигурации для сервера выглядит так:
application: {
components: {
comet: {
className: 'Jii.comet.server.Server',
host: '0.0.0.0',
port: 3100
},
// ...
}
}
Полный список свойств, методов и событий представлен ниже.
Jii.comet.server.HubServer
Свойства
- listenActions (boolean) — принимать ли запросы на вызов действий от клиентов. По умолчанию, true.
- hub (object) — хаб, компонент или его конфигурация, имплементирующий интерфейс Jii.comet.server.hub.HubInterface для обмена сообщениями между процессами или серверами. По умолчанию создается компонент Jii.comet.server.hub.Redis.
- queue (object) — очередь, компонент или его конфигурация, имплементирующий интерфейс Jii.comet.server.queue.QueueInterface для накопления очереди вызовов действий (actions). По умолчанию создается компонент Jii.comet.server.queue.Redis.
Методы
- start() — открывает соединение для компонентов hub и queue, начинает слушать сообщения из каналов.
- stop() — разрывает все соединения компонентов и перестает слушать информацию из вне.
- sendToChannel(channel, data) — отправляет сообщение в канал в рамках хаба. Здесь channel — (string) название канала, а data — (string|object|*) данные для отправки.
- sendToConnection(id, data) — как и предыдущий метод, отправляет данные, но не в канал, а напрямую в соединение. Здесь id (string) — это идентификатор соединения, взятый из Jii.comet.server.Connection, а data — (string|object|*) данные для отправки.
- on(name, handler, data, isAppend) и off(name, handler) — методы для подписки и отписки на события. Список событий описан ниже, описание аргументов можно найти в разделе событий.
- hasChannelHandlers(name) — Возвращает true, если на канал name (string) есть подписчики в рамках данного процесса.
События
- channel — Событие запускается при любом входящем сообщении в любой канал. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.ChannelEvent с параметрами channel (string) и message (string).
- channel:%my_channel_name% — Событие запускается при любом входящем сообщении в канал, указанный после
message — Событие запускается при любом входящем сообщении в хаб. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.server.MessageEvent с параметром message (string).
Jii.comet.server.Server
Наследует вышеперечисленные свойства, методы и события и добавляет следующие:
Свойства
- host (string) — хост, ожидающий входящие соединения от клиентов. По-умолчанию, 0.0.0.0.
- port (number) — порт для входящих соединений. По-умолчанию, 4100.
- transport (object) — компонент или его конфигурация, имплементирующий интерфейс Jii.comet.server.transport.TransportInterface для обмена сообщениями непосредственно с клиентами (браузером).
Методы
- subscribe(connectionId, channel) и unsubscribe(connectionId, channel) — подписывает и отписывает соединение на заданный канал. Jii рекомендует делать подписку на канал именно на сервере, чтобы избежать гонки сообщений. В методах connectionId (string) — это идентификатор соединения, взятый из Jii.comet.server.Connection, а channel (string) — имя канала.
События
- addConnection — Событие возникает при присоединении нового клиента. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.server.ConnectionEvent с параметром connection (Jii.comet.server.Connection).
- removeConnection — Событие возникает при потере соединения с клиентом. Аналогично предыдущему, первым аргументом обработчика будет передан экземпляр класса Jii.comet.server.ConnectionEvent.
Jii.comet.server.Connection
Содержит информацию о соединении с клиентом
Свойства
- id (string) — идентификатор соединения. Используется для отправки сообщений напрямую в соединение.
- request (Jii.comet.server.Request) — информация о соединении: заголовки, ip адрес, порт подключения и так далее.
- originalConnection (object) — внутренний экземпляр соединения, полученный от транспорта. Специфичен для каждого транспорта.
Комет-клиент
На клиенте немного проще: есть один компонент Jii.comet.client.Client, который подключается к одному из серверов и обменивается данными.
application: {
components: {
comet: {
className: 'Jii.comet.client.Client',
serverUrl: 'http://localhost:4401/comet',
autoOpen: true
},
// ...
}
}
Полный список свойств, методов и событий представлен ниже.
Jii.comet.client.Client
Свойства
- transport (object) — компонент или его конфигурация, имплементирующий интерфейс Jii.comet.client.transport.TransportInterface для обмена сообщениями с сервером.
- plugins (object) — набор компонентов или их конфигурация, каждый из которых имплементирует интерфейс Jii.comet.client.plugin.PluginInterface для расширения возможностей комет клиента.
- workersCount (number|null) — максимальное количество воркеров сервера. Эта настройка используется для балансировки нагрузки на клиенте. По-умолчанию, null — отключена.
- autoOpen (boolean) — если указано true, то при инициализации приложения комет клиент автоматически будет подключаться к серверу. По-умолчанию, true.
События
- open — Событие запускается при успешном открытии соединения.
- close — Событие запускается при закрытии соединения.
- beforeSend — Событие запускается при любом исходящем сообщении. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.client.MessageEvent с параметром message. Вы можете изменить отправляемое сообщение, изменяя параметр message, на сервер отправятся данные из параметра message.
- channel — Событие запускается при любом входящем сообщении в любой подписанный канал. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.ChannelEvent с параметрами channel (string) и message (string).
- channel:%my_channel_name% — Событие запускается при любом входящем сообщении в подписанный канал, указанный после :. Например, при вызове Jii.app.comet.on('channel:test', ...) происходит подписка на сообщения в канал test. Как и выше, в обработчик события первым аргументом будет передан экземпляр класса Jii.comet.ChannelEvent с параметрами channel (string) и message (string).
- message — Событие запускается при любом входящем сообщении. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.server.MessageEvent с параметром message (string).
- beforeRequest — Событие запускается перед тем, как клиент пытается отправить на сервер запрос на выполнения действия. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.client.RequestEvent с параметрами route (string) и params (object). Вы можете изменить параметры запроса в этом событии, на сервер отправятся данные из параметра params.
- request — Событие запускается при ответе сервера на запрошенное ранее действие. Первым аргументом обработчика будет передан экземпляр класса Jii.comet.client.RequestEvent с параметрами route (string) и params (object).
Плагины
Плагины позволяют расширять возможности комет клиента. По-умолчанию, установлен плагин для автоматического восстановления соединения Jii.comet.client.plugin.AutoReconnect. Плагин очень прост в создании и подключении. Интерфейс плагина Jii.comet.client.plugin.PluginInterface не требует имплементации методов, а лишь предоставляет доступ к комет-клиенту через параметр comet, через который можно подписываться на события комета. Устанавливается и настраивается плагин через конфигурацию, путем добавления его в секцию plugins:
application: {
components: {
comet: {
className: 'Jii.comet.client.Client',
// ...
plugins: {
autoReconnect: {
enable: false
},
myPlugin: {
className: 'app.components.MyCometPlugin'
}
}
},
// ...
}
}
Как это работает
С первого взгляда, jii-comet — это обертка над библиотекой комет-транспорта (sockjs, socket.io — на выбор), однако это не так. От библиотеки транспорта берется только функционал доставки сообщения на клиент и обратно. Реализация каналов, подписок, масштабирования и дополнительных фишек сделано именно в jii-comet. Архитектурно «под капотом» jii-comet разбит на следующие составляющие:
Сервер:
- Транспорт (Jii.comet.server.transport.TransportInterface) — абстракция для обмена сообщениями между клиентом и сервером.
- Хаб (Jii.comet.server.hub.HubInterface) — абстракция для обмена сообщениями между процессами и серверами.
- Очередь (Jii.comet.server.queue.QueueInterface) — абстракция для накопления вызовов действий.
- Соединение (Jii.comet.server.Connection) — экземпляр данного класса содержит информацию о соединении и доступен как компонент контекста.
Клиент:
- Транспорт (Jii.comet.client.transport.TransportInterface) — абстракция для обмена сообщениями между клиентом и сервером.
- Плагин (Jii.comet.client.plugin.PluginInterface) — абстракция для расширения возможностей комет клиента. По-умолчанию, установлен плагин для автоматического восстановления соединения Jii.comet.client.plugin.AutoReconnect.
Все эти абстракции могут настраиваться и переопределяться через конфигурацию приложения и контекста.
Примеры
- несколько клиентов
- несколько серверных процессов только для поддержания соединений (Jii.comet.server.Server и listenActions = false)
- несколько серверных процессов для поддержания соединений и обработки действий (Jii.comet.server.Server и listenActions = true)
- несколько серверных процессов только для обработки действий (Jii.comet.server.HubServer и listenActions = true)
Пример: Клиент отправляет сообщение в канал
- Клиент отправляет сообщение в канал test.
- Серверный процесс, поддерживающий соединение принимает это сообщение и отправляет в хаб (Jii.comet.server.hub.HubInterface).
- Хаб рассылает это сообщение всем серверным процессам, которые подписаны на данный канал (или подписаны их соединения).
- Серверные процессы получают сообщение и отправляют соединениям, которые на него подписаны.
Пример: Клиент вызывает действие на сервере
- Клиент отправляет запрос на выполнение действия site/test.
- Серверный процесс, поддерживающий соединение принимает его и скидывает в очередь действий (Jii.comet.server.queue.QueueInterface), а так же через хаб (Jii.comet.server.hub.HubInterface) рассылает уведомление всем серверам, обрабатывающим действия (listenActions = true) о том, что очередь обновилась.
- Один из серверных процессов с listenActions = true вытаскивает запрос из очереди (методом lpop в имплементации Redis'а) и запускает у себя действие.
- После выполнения действия, серверный процесс отправляет результат действия (response) обратно клиенту. Поскольку данный серверный процесс не имеет прямого соединения с клиентом, он отправляет его в хаб (Jii.comet.server.hub.HubInterface).
- Хаб отправляет сообщение серверному процессу, подписанному на сообщения для этого соединения.
- Серверный процесс, поддерживающий это соединение получает сообщение и отправляет его непосредственно клиенту.
В третьем пункте запрос из очереди мог вытянуть любой серверный процесс с listenActions = true. Работает это по принципу «кто успел» и, как правило, успевать будет наименее нагруженный сервер. Таким образом, производится балансировка вызовов действий между серверами и их процессами.
Внутренний канал связи
Jii-comet имеет абстракцию, позволяющую использовать любую библиотеку в качестве внутреннего канала связи между клиентом и сервером. На данный момент сделан адаптер для библиотеки sockjs, в будущем появится и для socket.io.
В конфигурации транспорт объявляется в секции transport. По-умолчанию, используется класс Jii.comet.server.transport.Sockjs на севере и Jii.comet.client.transport.Sockjs на клиенте.
Транспорт можно конфигурировать и переопределять через конфигурацию:
comet: {
className: 'Jii.comet.client.Client',
transport: {
className: 'Jii.comet.client.transport.Sockjs',
transports: [
'websocket',
'xhr-streaming',
'xdr-streaming',
'jsonp-polling'
]
}
}
The End
Сайт фреймворка — jiiframework.ru
GitHub — github.com/jiisoft
Пожелания и предложения отправляйте на affka@affka.ru
Нравится идея фреймворка? Ставь звезду на гитхабе!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Каким комет решением вы пользуетесь для node.js?
14.58% sockjs7
75% socket.io36
10.42% другое5
Проголосовали 48 пользователей. Воздержались 29 пользователей.