Межпроцессная репликация объектов с помощью QtRemoteObjects

в 22:18, , рубрики: c++, IPC, qt, RPC, Программирование, метки: , ,

7 октября 2014 года в публичном доступе появились исходники Qt-модуля QtRemoteObjects. Модуль создан в недрах Ford Motor Company (автор Brett Stottlemyer). Вещь, на мой взгляд, очень перспективная. Модуль позволяет, например, передавать сигналы между объектами по сети. Но этим возможности модуля не ограничиваются. Более точно суть модуля описывает его предыдущее наименование — Replica, так как объекты «реплицируются» между процессами.

Межпроцессная репликация объектов с помощью QtRemoteObjects - 1

Ключевой идеей QtRemoteObjects, которая качественно отличает его от других способов межпроцессного взаимодействия/удаленного вызова процедур, является идея полностью продублировать Qt-объект в другие процессы. Это значит, что все изменения свойств (properties) в объекте — источнике отражаются (с уведомлением посредством сигналов) в объекте-реплике. Любые сигналы, которые эмитируются объектом-источником будут также эмитированы в каждом объекте-реплике. Также можно устанавливать свойства, вызывать слоты и в объекте-реплике, при этом запросы отправляются объекту-источнику, который их обрабатывает и затем изменения отражаются в других объектах-репликах посредством сигналов или с помощью изменения свойств. В результате все объекты (включая объект-источник) синхронизируются. При этом вся сложность межпроцессного взаимодействия скрыта внутри QtRemoteObjects.

Сборка и установка модуля

1. Установить приватные заголовочные файлы для вашей версии Qt 5

В своей системе выполнено путем установки пакета qtbase5-private-dev

2. Получить исходники модуля

git clone gitorious.org/qtplayground/qtremoteobjects.git

3. Собрать и установить модуль

Собирал проект в QtCreator, используя теневую сборку. В результате в директории build-qtremoteobjects-qt5_3_0-Release появились все необходимые файлы. Если затем в данной директории выполнить make install то заголовочные файлы модуля, библиотека и кодогенератор для rep файлов будут установлены в соответствующие директории. Единственное, что не было установлено — это содержимое директории qtremoteobjects/mkspecs/features, его нужно скопировать в директорию /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features.

Если есть проблемы с make install, то можно все сделать «вручную»: скопировать библиотеку libQt5RemoteObjects.so (dll) в директорию библиотек Qt а директорию /include/QtRemoteObjects в директорию подключаемых файлов Qt соответственно. Также в «ручном» режиме понадобится скопировать бинарник из директории сборки bin/repc в директорию бинарников Qt (в моем случае это /usr/lib/x86_64-linux-gnu/qt5/bin/repc ).

Использование модуля

Наличие объекта — источника и объекта — реплики навевает мысль о клиент-серверной архитектуре. Поэтому для демонстрации работы модуля создадим два проекта — клиент и сервер. Объект-источник на сервере будет эмитировать сигнал, в параметрах которого будет передаваться текст, а в слоте клиента, подключенного к сигналу объекта-реплики, передаваемый текст будет обрабатываться.
Пример очень простой, но на таком примере будет проще показать работу модуля. Две картинки получившихся приложений и далее код.

Сервер
Межпроцессная репликация объектов с помощью QtRemoteObjects - 2
Клиент
Межпроцессная репликация объектов с помощью QtRemoteObjects - 3

Проекты клиента и сервера

У нас будет одна общая директория (например remoteobj) и 2 поддиректории: client и server, в которой будут располагаться проекты двух приложений. В файлах проектов обоих приложений необходимо подключить модуль.

QT += remoteobjects

Файлы .rep

Файлы с расширением .rep используются для описания интерфейса объекта, который будет использоваться для межпроцессного взаимодействия. В директории, родительской по отношению к директории проектов клиента и сервера создаем тектовый файл MessageSender.rep со следующим содержимым:

#include <QtCore>
class MessageSender
{
    SIGNAL(sendMessage(const QString &message));
};

Помните я писал про файлы из директории mkspecs/features? Именно в этих файлах описываются привила для обработки файлов rep. Для того чтобы они были обработаны в ходе сборки, в файлах проектов необходимо добавить следующие строчки:
для клиента

REPC_REPLICA += ../MessageSender.rep

для сервера

REPC_SOURCE += ../MessageSender.rep

Во время сборки из файла MessageSender.rep будут сгенерированы заголовочные файлы

для клиента - rep_MessageSender_replica.h


#ifndef REP_MESSAGESENDER_REPLICA_H
#define REP_MESSAGESENDER_REPLICA_H

// This is an autogenerated file.
// Do not edit this file, any changes made will be lost the next time it is generated.

#include <QObject>
#include <QVariantList>
#include <QMetaProperty>

#include <QRemoteObjectNode>
#include <QRemoteObjectReplica>
#include <QRemoteObjectPendingReply>

#include <QtCore>
class MessageSenderReplica : public QRemoteObjectReplica
{
    Q_OBJECT
    Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "MessageSender")
    friend class QRemoteObjectNode;
private:
    MessageSenderReplica() : QRemoteObjectReplica() {}
    void initialize()
    {
        QVariantList properties;
        properties.reserve(0);
        setProperties(properties);
    }
public:
    virtual ~MessageSenderReplica() {}


Q_SIGNALS:
    void sendMessage(const QString & message);
};

#endif // REP_MESSAGESENDER_REPLICA_H

для сервера - rep_MessageSender_source.h


#ifndef REP_MESSAGESENDER_SOURCE_H
#define REP_MESSAGESENDER_SOURCE_H

// This is an autogenerated file.
// Do not edit this file, any changes made will be lost the next time it is generated.

#include <QObject>
#include <QVariantList>
#include <QMetaProperty>

#include <QRemoteObjectNode>
#include <qremoteobjectsource.h>

#include <QtCore>
class MessageSenderSource : public QObject
{
    Q_OBJECT
    Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "MessageSender")
    friend class QRemoteObjectNode;
public:
    MessageSenderSource(QObject *parent = Q_NULLPTR) : QObject(parent)
    {
    }
public:
    virtual ~MessageSenderSource() {}


Q_SIGNALS:
    void sendMessage(const QString & message);
};

class MessageSenderSimpleSource : public QObject
{
    Q_OBJECT
    Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "MessageSender")
    friend class QRemoteObjectNode;
public:
    MessageSenderSimpleSource(QObject *parent = Q_NULLPTR) : QObject(parent)
    {
    }
public:
    virtual ~MessageSenderSimpleSource() {}


Q_SIGNALS:
    void sendMessage(const QString & message);
};

template <class ObjectType>
struct MessageSenderSourceAPI : public SourceApiMap
{
    MessageSenderSourceAPI()
    {
        _properties[0] = 0;
        _signals[0] = 1;
        _signals[1] = qtro_signal_index<ObjectType>(&ObjectType::sendMessage, static_cast<void (QObject::*)(QString)>(0),signalArgCount+0,signalArgTypes[0]);
        _methods[0] = 0;
    }

    QString name() const Q_DECL_OVERRIDE { return QStringLiteral("MessageSender"); }
    int propertyCount() const Q_DECL_OVERRIDE { return _properties[0]; }
    int signalCount() const Q_DECL_OVERRIDE { return _signals[0]; }
    int methodCount() const Q_DECL_OVERRIDE { return _methods[0]; }
    int sourcePropertyIndex(int index) const Q_DECL_OVERRIDE { return _properties[index+1]; }
    int sourceSignalIndex(int index) const Q_DECL_OVERRIDE { return _signals[index+1]; }
    int sourceMethodIndex(int index) const Q_DECL_OVERRIDE { return _methods[index+1]; }
    int signalParameterCount(int index) const Q_DECL_OVERRIDE { return signalArgCount[index]; }
    int signalParameterType(int sigIndex, int paramIndex) const Q_DECL_OVERRIDE { return signalArgTypes[sigIndex][paramIndex]; }
    int methodParameterCount(int index) const Q_DECL_OVERRIDE { return methodArgCount[index]; }
    int methodParameterType(int methodIndex, int paramIndex) const Q_DECL_OVERRIDE { return methodArgTypes[methodIndex][paramIndex]; }
    int propertyIndexFromSignal(int index) const Q_DECL_OVERRIDE
    {
        Q_UNUSED(index);
        return -1;
    }
    const QByteArray signalSignature(int index) const Q_DECL_OVERRIDE
    {
        switch (index) {
        case 0: return QByteArrayLiteral("sendMessage(QString)");
        }
        return QByteArrayLiteral("");
    }
    const QByteArray methodSignature(int index) const Q_DECL_OVERRIDE
    {
        Q_UNUSED(index);
        return QByteArrayLiteral("");
    }
    QMetaMethod::MethodType methodType(int) const Q_DECL_OVERRIDE
    {
        return QMetaMethod::Slot;
    }
    const QByteArray typeName(int index) const Q_DECL_OVERRIDE
    {
        Q_UNUSED(index);
        return QByteArrayLiteral("");
    }

    int _properties[1];
    int _signals[2];
    int _methods[1];
    int signalArgCount[1];
    const int* signalArgTypes[1];
    int methodArgCount[0];
    const int* methodArgTypes[0];
};
#endif // REP_MESSAGESENDER_SOURCE_H

Данные файлы необходимо подключить в заголовочных файлах клиента и сервера — соответственно их принадлежности.

Клиент

Первым разберем клиент, так как он проще сервера.
Нужно добавить член класса клиента:

QRemoteObjectNode clientNode;

И потом в инициализирующей функции (или прямо в конструкторе) пишем:


clientNode = QRemoteObjectNode::createNodeConnectedToRegistry(); //создаем ноду
QRemoteObjectReplica *sender = m_client.acquire< MessageSenderReplica >(); //получаем указатель на реплику
connect(sender, SIGNAL(sendMessage(const QString &)), this, SLOT(appendMessage(const QString &))); //коннектим сигнал к слоту

В слоте appendMessage полученная строка просто добавляется в список и поэтому его описание я пропущу и перейду к описанию клиента.

Сервер

Так как в сгенерированных заголовочных файлах находится только интерфейс, то для выполнения полезной работы нашим объектом — источником, необходимо добавить ему функционал. Для этого наследуемся от сгенерированного класса и определяем слот:


class MessageSender : public MessageSenderSource
{
    Q_OBJECT

public slots:
    void postMessage(const QString &message);
};

Реализация этого слота будет просто эмитировать сигнал.


void MessageSender::postMessage(const QString &message)
{
    emit sendMessage(message);
}

Теперь добавляем следующие члены класса сервера:


    MessageSender *serverSender; //описанный выше класс
    QRemoteObjectNode registryHostNode; //нода регистра
    QRemoteObjectNode objectNode; //нода объекта

И в инициализирующей функции (или прямо в конструкторе) пишем:


    connect(ui->sendButton, SIGNAL(clicked()), this, SLOT(startSendMessage())); //обработка кнопки
    registryHostNode = QRemoteObjectNode::createRegistryHostNode(); //создаем ноду регистра
    objectNode = QRemoteObjectNode::createHostNodeConnectedToRegistry(); //создаем ноду объекта
    serverSender = new MessageSender(); //создаем объект-источник
    objectNode.enableRemoting(serverSender);//биндим его к ноде

В слоте startSendMessage() будет вызываться слот объекта-источника:


QString messageText = ui->messageTextEdit->text();
serverSender->postMessage(messageText);

Теперь запускаем приложения: сначала сервер а потом клиент.

Работа по сети

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

Для взаимодействия по сети нужно модифицировать код создания нод
на стороне сервера


        objectNode = QRemoteObjectNode::createHostNode(QUrl("tcp://localhost:9999"));
        //registryHostNode не используется

на стороне клиента


        clientNode = QRemoteObjectNode();
        clientNode.connect(QUrl("tcp://localhost:9999"));
Вместо заключения

В статье не рассматривается использование свойств (properties). Пример использования свойств и слотов (которые также описываются в файле rep) можно увидеть в examples, поставляемых вместе с модулем.

Ссылка на архив с исходниками сервера и клиента.
Презентация c Qt Developers Days 2014 North America

Автор: snasoft

Источник

Поделиться

* - обязательные к заполнению поля