在QtRO中Replica实际上有两种:静态的和动态的。所谓静态,就是Source和Replica都通过rep文件作为接口定义,进而连接通信;而所谓动态是指Replica这边不再需要rep文件,而是运行时动态获取接口定义。
在这个例子中我们创建两个工程:Server和Directconnect。在Server中我们将实现功能类,一个能接收消息并发送消息的类;而在Directconnect中我们将使用该功能类。
首先我们定义接口。创建一个rep文件transfernews.rep,内容很简单,一个属性和两个信号、一个槽函数:
#include
POD Foo(QList bar);
class TransferNews
{
PROP(QString lastMessage = "");
SIGNAL(serverTime(QString));
SIGNAL(testPod(Foo foo));
SLOT(void reveiceMessage(QString));
};
lastMessage是最新的消息;serverTime是一个server定时发射的信号;testPod也是一个信号,为了测试POD类型;receiveMessage是一个槽函数,连接server接收到的消息。
首先在.pro文件中加入QtRO模块:
QT += remoteobjects
然后以SOURCE的解析方式加入rep文件:
REPC_SOURCE = transfernews.rep
repc能够根据transfernews.rep内容自动生成rep_transfernews_source.h。其中有多个类定义,我们一般选择类名以Source结尾的那个类作为我们的基类,它的定义大概是:
class TransferNewsSource : public QObject
{
Q_OBJECT
Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "TransferNews")
Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_SIGNATURE, "4a72fbe23576cac923df24c7a74d1bce42a97db6")
Q_PROPERTY(QString lastMessage READ lastMessage WRITE setLastMessage NOTIFY lastMessageChanged)
public:
explicit TransferNewsSource(QObject *parent = nullptr) : QObject(parent)
{
qRegisterMetaType();
qRegisterMetaTypeStreamOperators();
qRegisterMetaType>();
qRegisterMetaTypeStreamOperators>();
}
public:
virtual ~TransferNewsSource() {}
virtual QString lastMessage() const = 0;
virtual void setLastMessage(QString lastMessage) = 0;
Q_SIGNALS:
void lastMessageChanged(QString lastMessage);
void serverTime(QString __repc_variable_1);
void testPod(Foo foo);
public Q_SLOTS:
virtual void pushLastMessage(QString lastMessage)
{
setLastMessage(lastMessage);
}
virtual void reveiceMessage(QString __repc_variable_1) = 0;
private:
friend class QT_PREPEND_NAMESPACE(QRemoteObjectNode);
};
可以看到,repc已经自动帮我们生成了各种Qt元信息。我们只需要派生这个类,然后把那几个虚函数实现了就可以了:
#ifndef TRANSFERNEWS_H
#define TRANSFERNEWS_H
#include
#include
#include "rep_transfernews_source.h"
class TransferNews : public TransferNewsSimpleSource
{
Q_OBJECT
public:
explicit TransferNews(QObject *parent = nullptr);
~TransferNews();
public slots:
void reveiceMessage(QString msg) override;
void sendServerTime();
private:
QTimer *m_timer;
};
#endif // TRANSFERNEWS_H
源文件
#include "transfernews.h"
#include
#include
TransferNews::TransferNews(QObject *parent)
: TransferNewsSimpleSource(parent)
{
m_timer = new QTimer(this);
m_timer->start(5000);
connect(m_timer, SIGNAL(timeout()), this, SLOT(sendServerTime()));
}
TransferNews::~TransferNews()
{
m_timer->stop();
}
void TransferNews::reveiceMessage(QString msg)
{
setLastMessage(msg);
qDebug() << msg;
}
void TransferNews::sendServerTime()
{
QString message = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
emit serverTime(message);
QList list;
list << "123" << "456" << "789";
emit testPod(Foo(list));
qDebug() << message;
}
这样我们的功能类算是写好了。接下去我们需要把它分享出去,代码写在main.cpp里:
#include "transfernews.h"
#include
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
TransferNews transferNews;
QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));
srcNode.enableRemoting(&transferNews);
return app.exec();
}
这样我们的服务端算是写好了。
同样我们需要在.pro中加入QtRO模块,然后加入rep文件。但是注意这里我们要用REPLICA的模式处理该rep文件:
REPC_REPLICA = transfernews.rep
客户端我们使用Qt Widget来写。所以我们在main.cpp中获取Replica,然后将它传进我们的Widget环境中:
#include "mainwindow.h"
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QRemoteObjectNode repNode;
repNode.connectToNode(QUrl(QStringLiteral("local:replica")));
QSharedPointer ptr;
ptr.reset(repNode.acquire());
MainWindow w(ptr);
w.show();
return app.exec();
}
然后在mainwindow中构建我们的界面
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include "directconnect.h"
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QSharedPointer ptr, QWidget *parent = nullptr);
public Q_SLOTS:
void onSend();
void onReceive(QString);
protected:
void setupUi();
private:
QTextEdit *m_textEdit;
QLineEdit *m_lineEdit;
QToolButton *m_btn;
DirectConnect *m_dc;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include
#include
#include
MainWindow::MainWindow(QSharedPointer ptr, QWidget *parent)
: QWidget(parent)
{
setMinimumSize(600, 400);
setupUi();
m_dc = new DirectConnect(ptr);
connect(m_dc, SIGNAL(sig_newMessage(QString)), this, SLOT(onReceive(QString)));
}
void MainWindow::setupUi()
{
m_textEdit = new QTextEdit;
QTextCursor textCursor = m_textEdit->textCursor();
QTextBlockFormat textBlockFormat;
textBlockFormat.setBottomMargin(10);
textCursor.setBlockFormat(textBlockFormat);
m_textEdit->setTextCursor(textCursor);
m_textEdit->setFocusPolicy(Qt::NoFocus);
m_lineEdit = new QLineEdit;
m_lineEdit->setFixedHeight(30);
m_btn = new QToolButton;
m_btn->setFixedSize(80, 30);
m_btn->setText("Send");
m_btn->setFocusPolicy(Qt::NoFocus);
connect(m_btn, &QToolButton::clicked, this, &MainWindow::onSend);
QHBoxLayout *hlayout = new QHBoxLayout;
hlayout->addWidget(m_lineEdit);
hlayout->addWidget(m_btn);
hlayout->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(m_textEdit);
layout->addLayout(hlayout);
layout->setSpacing(8);
layout->setContentsMargins(10, 10, 10, 10);
setLayout(layout);
}
void MainWindow::onSend()
{
QString msg = m_lineEdit->text();
m_lineEdit->clear();
emit m_dc->sig_sendMessage(msg);
}
void MainWindow::onReceive(QString msg)
{
m_textEdit->append(msg);
}
#ifndef DIRECTCONNECT_H
#define DIRECTCONNECT_H
#include
#include
#include "rep_transfernews_replica.h"
class DirectConnect : public QObject
{
Q_OBJECT
public:
DirectConnect(QSharedPointer ptr);
~DirectConnect() override;
signals:
void sig_sendMessage(QString);
void sig_newMessage(QString);
protected slots:
void slot_lastMessageChanged(QString);
void slot_serverTimeChanged(QString);
void slot_testPod(Foo);
void slot_stateChanged(QRemoteObjectReplica::State, QRemoteObjectReplica::State);
private:
QSharedPointer m_ptr;
};
#endif // DIRECTCONNECT_H
#include "directconnect.h"
DirectConnect::DirectConnect(QSharedPointer ptr)
: QObject(nullptr), m_ptr(ptr)
{
QObject::connect(this, SIGNAL(sig_sendMessage(QString)), m_ptr.data(), SLOT(reveiceMessage(QString)));
QObject::connect(m_ptr.data(), SIGNAL(lastMessageChanged(QString)), this, SLOT(slot_lastMessageChanged(QString)));
QObject::connect(m_ptr.data(), SIGNAL(serverTime(QString)), this, SLOT(slot_serverTimeChanged(QString)));
QObject::connect(m_ptr.data(), SIGNAL(testPod(Foo)), this, SLOT(slot_testPod(Foo)));
QObject::connect(m_ptr.data(), SIGNAL(stateChanged(QRemoteObjectReplica::State,QRemoteObjectReplica::State)),
this, SLOT(slot_stateChanged(QRemoteObjectReplica::State,QRemoteObjectReplica::State)));
}
DirectConnect::~DirectConnect()
{
}
void DirectConnect::slot_lastMessageChanged(QString msg)
{
//pros test
QString text = "Pros: " + msg;
emit sig_newMessage(text);
}
void DirectConnect::slot_serverTimeChanged(QString msg)
{
//signal test
QString text = "Signal: " + msg;
emit sig_newMessage(text);
}
void DirectConnect::slot_testPod(Foo foo)
{
//Pod test
QList list = foo.bar();
for(auto itor = list.begin(); itor != list.end(); itor++)
{
QString text = "POD: " + *itor;
emit sig_newMessage(text);
}
}
void DirectConnect::slot_stateChanged(QRemoteObjectReplica::State new_state, QRemoteObjectReplica::State old_state)
{
//State test
QMetaEnum metaEnum = QMetaEnum::fromType();
QString text = "State: ";
text.append(metaEnum.valueToKey(new_state));
text.append("->");
text.append(metaEnum.valueToKey(old_state));
emit sig_newMessage(text);
}
可以看到,我们通过connect将功能类的信号槽进行绑定,用起来就好像功能类在我们当前客户端工程本地一样,非常直观。
Server和Directconnect的启动顺序是可以任意的,一般是Server先启动,然后Directconnect。但是QtRO允许Directconnect先启动,然后等待Server。
启动Directconnect后我们发送消息,可以看到成功显示从功能类返回的消息,如下图:
如果运行多个Directconnect程序我们可以看到所有的Directconnect的“消息内容”都是同步的,后启动的Directconnect能够自动收到功能类的属性值。这也是QtRO一个非常重要的特性,即:后连接的Directconnect能够收到所有server改变了的属性值,即使该属性是在该server创建之前就改变的。