
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
Нравится идея фреймворка? Ставь звезду на гитхабе!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (7)

uLow
15.10.2015 04:04-1Заглянул в исходники… навязался вопрос — а при чем тут Yii вообще?

uLow
15.10.2015 04:09Jii — это фреймфорк, архитектура и API которого базируется на PHP фреймворке Yii 2.0, взяв из него лучшие стороны и сохраняя преимущества JavaScript.
Yii позаимствовал архитектуру у RoR (тогда уж Jails, а не Jii).
А API Yii не используется ни коем разом.
Так зачем вообще упоминать Yii? Пиар за чужой счет?
auine
15.10.2015 21:55Нравится человеку Yii, некоторые принципы пытается вынести на фронт. Решил созвучно назвать свой труд.
Ваш ник uLow, пиаритесь за счет uCoz?
uLow
16.10.2015 00:46-1Фраза «базируется на ХХХ» подразумевает, что используется какая-то часть.
К примеру о фреймворках — Silex и Symfony, Lumen и Laravel, Rails и Ruby…
А Jii и Yii всего лишь созвучны. Это не Yii на Javascript. Это просто «yet another framework» для Node.js. Да, в чем-то есть привкус Yii, возможно разработчик думал о Yii в момент написания. Но Jii не является тем, чем его пытается выдать автор.
affka
16.10.2015 04:13Вы слова то не выкидывайте. «Архитектура и API» базируется. А как может архитектура использовать часть архитектуры?.. Она может только повторять.
Мне кажется вы сами себе уже противоречите в формулировке: «Jii != Yii на JavaScript, а Jii = Yii для Node.Js»
> Да, в чем-то есть привкус Yii,
вы видимо вообще не смотрели фреймворк Jii, ваши суждение в корне неверны
          
 
SamDark
Ну и зачем постить в блоге Yii?
affka
Убрал