1 概述
Qt Remote Object简称QtRO,这是Qt5.9以后官方推出来的新模块,专门用于进程间通信(IPC)。QtRO本质上是一个点对点的通信网络。每个进程通过QRemoteObjectNode接入QtRO网络。功能提供节点(可以理解为服务器)需要使用QRemoteObjectHost将一个提供实际功能的QObject派生类注册进QtRO网络中,然后其他使用该功能的程序则通过各自的QRemoteObjectNode连接到该Host上,然后acquire一个该功能对象的Replica。等到该Replica初始化好后,该程序就能够使用Replica中的信号、槽以及属性,就好像功能类就在本地一样。QTRO分为两种Replica,一种时动态的Replica,一种是静态的Replica。
2静态和动态的对比
静态Replica:
优势
拥有明确的定义,更适合在C++中使用(因为有repc生成的头文件)。
支持POD等复杂结构的定义。
更高效。因为结构定义都已经在C++中定义好了,不需要动态传输、构建,节省了开销。
劣势
Source端和Replica端必须严格使用同一版本的rep文件,即使rep文件内只是添加了一行注释,否则会连接不上。
动态Replica
优势
由于Client端不需要rep文件,所以Server端可以随时修改,这就避免了静态模式下的缺点。
劣势
不支持POD等复杂结构定义。
必须等初始化后才能使用,给编程增加了额外复杂度,同时增加了构建连接的额外开销。这个是动态这个特性决定的。
pod类型简单的理解就是float、int、string、class、struct等类型。
3 静态Replica
服务端
1创建rep文件
rep文件是一种DSL(Domain Specific Language),专门用于定义QtRO接口。在编译的时候,该文件会首先经过repc.exe这个程序处理,生成对应的头文件和源文件。
commoninterface.rep
class CommonInterface
{
SIGNAL(sigMessage(QString msg)) //server下发消息给client
SLOT(void onMessage(QString msg)) //server接收client的消息
}
2编译
添加QtRO模块
QT += remoteobjects
添加rep文件
REPC_SOURCE += \
./Reps/CommonInterface.rep
编译,然后在程序的输出目录可以找到生成的rep头文件
3实现功能类
创建一个类,继承于自动生成的这个类,并实现其中所有的虚函数。
CommonInterface.h
#ifndef COMMONINTERFACE_H
#define COMMONINTERFACE_H
#include "rep_CommonInterface_source.h"
class CommonInterface:public CommonInterfaceSource
{
Q_OBJECT
public:
explicit CommonInterface(QObject * parent = nullptr);
//接收消息
void onMessage(QString msg) override;
//发送消息
void sendMsg(QString msg);
signals:
void sigReceiveMsg(QString);
};
#endif // COMMONINTERFACE_H
CommonInterface.cpp
#include "CommonInterface.h"
CommonInterface::CommonInterface(QObject *parent):
CommonInterfaceSource(parent)
{
}
void CommonInterface::onMessage(QString msg)
{
emit sigReceiveMsg(msg);
}
void CommonInterface::sendMsg(QString msg)
{
sigMessage(msg);
}
4实现Server的主逻辑
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "CommonInterface.h"
#include <QRemoteObjectHost>
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
void init();
private slots:
void onReceiveMsg(QString msg);
void sendBtnClicked();
void lineRetrunPressed();
private:
Ui::Widget *ui;
CommonInterface* m_pInterface=nullptr;
QRemoteObjectHost* m_pHost=nullptr;
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include "ui_Widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
init();
}
Widget::~Widget()
{
delete ui;
}
void Widget::init()
{
setWindowTitle("This is Server");
ui->inputLine->setFocus();
m_pHost = new QRemoteObjectHost(this);
m_pHost->setHostUrl(QUrl("local:interfaces"));
m_pInterface = new CommonInterface(this);
m_pHost->enableRemoting(m_pInterface);
connect(m_pInterface,&CommonInterface::sigReceiveMsg,this,&Widget::onReceiveMsg);
connect(ui->sendBtn,&QPushButton::clicked,this,&Widget::sendBtnClicked);
connect(ui->inputLine,&QLineEdit::returnPressed,this,&Widget::lineRetrunPressed);
}
void Widget::onReceiveMsg(QString msg)
{
QDateTime curDateTime=QDateTime::currentDateTime();
QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit->append(temp+QString(" Client: ") + msg);
}
void Widget::sendBtnClicked()
{
QString msg = ui->inputLine->text();
if(!msg.isEmpty()){
m_pInterface->sendMsg(msg);
}
QDateTime curDateTime=QDateTime::currentDateTime();
QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit->append(temp+QString(" Server: ") + msg);
ui->inputLine->clear();
}
void Widget::lineRetrunPressed()
{
sendBtnClicked();
}
注意
m_pHost->setHostUrl(QUrl("local:interfaces"));
字符串格式为"local:xxxx",其中xxxx必须是唯一的字符串,不同和其他程序有冲突,否则将会无法连接。
服务端到这里就OK了。
客户端
1创建rep文件夹
客户端共用一个rep文件,然后配置por文件
QT += remoteobjects
REPC_REPLICA += \
./Rep/CommonInterface.rep
注意,这里和Server端添加方式不一样,server端是REPC_SOURCE。
2编译
找到对应生成的rep头文件
和server端不同的是,client端不需要重新实现功能类,只需要在主窗口中连接server端即可。
3实现Client的主逻辑
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QRemoteObjectNode>
#include "rep_CommonInterface_replica.h"
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
void init();
private slots:
void onReceiveMsg(QString msg);
void sendBtnClicked();
void lineRetrunPressed();
private:
Ui::Widget *ui;
QRemoteObjectNode * m_pRemoteNode = nullptr;
CommonInterfaceReplica * m_pInterface = nullptr;
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include "ui_Widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
init();
}
Widget::~Widget()
{
delete ui;
}
void Widget::init()
{
setWindowTitle("This is Client");
ui->inputLine->setFocus();
m_pRemoteNode = new QRemoteObjectNode(this);
m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
m_pInterface = m_pRemoteNode->acquire<CommonInterfaceReplica>();
connect(m_pInterface,&CommonInterfaceReplica::sigMessage,
this,&Widget::onReceiveMsg);
connect(ui->sendBtn,&QPushButton::clicked,this,&Widget::sendBtnClicked);
connect(ui->inputLine,&QLineEdit::returnPressed,this,&Widget::lineRetrunPressed);
}
void Widget::onReceiveMsg(QString msg)
{
QDateTime curDateTime=QDateTime::currentDateTime();
QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit->append(temp+QString(" Server: ") + msg);
}
void Widget::sendBtnClicked()
{
QString msg = ui->inputLine->text();
if(!msg.isEmpty()){
m_pInterface->onMessage(msg); //调用槽发送消息给服务器
}
QDateTime curDateTime=QDateTime::currentDateTime();
QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit->append(temp+QString(" Client: ") + msg);
ui->inputLine->clear();
}
void Widget::lineRetrunPressed()
{
sendBtnClicked();
}
注意
m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
m_pInterface = m_pRemoteNode->acquire<CommonInterfaceReplica>();
这里的连接地址和server的必须保持一致,然后通过acquire(); 连接Server端。
客户端到这里就OK了。
运行截图
3 动态Replica
将上面的示例改成动态Replica。
Server端变化
m_pHost->enableRemoting(m_pInterface,QStringLiteral("Interfaces1"));
必须传入第二个参数name。
Client端变化
不需要rep文件,pro文件中直接去掉
#REPC_REPLICA += \
# ./Rep/CommonInterface.rep
然后再获取Replica的时候,需要用动态的版本
QRemoteObjectDynamicReplica * m_pInterface = nullptr;
m_pInterface = m_pRemoteNode->acquireDynamic("Interfaces1");//动态获取
此外,还有一个关键的点需要改动,因为没了rep文件,程序刚启动时不知道连接的Server端长啥样。所以动态Replica的内部原理是建立连接后,首先获得Source端的元信息,然后动态地在Replica端构建属性、信号和槽。这些构造完毕后,Replica会发送一个initialized的信号,这之后该Replica才能够真正被使用。
只有当Replica发出initialized信号后,该Replica才有Source端的元信息(属性、信号与槽),才能被使用。
setWindowTitle("This is Client");
ui->inputLine->setFocus();
m_pRemoteNode = new QRemoteObjectNode(this);
m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
m_pInterface=m_pRemoteNode->acquireDynamic("Interfaces1");//动态获取
//只有Replica初始化好了才能真正使用它,要不然connect无效
connect(m_pInterface, &QRemoteObjectDynamicReplica::initialized, this, &Widget::onInitConnect);
到这就全部改完了
Client端完整代码
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QRemoteObjectNode>
#include "rep_CommonInterface_replica.h"
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
void init();
private slots:
void onReceiveMsg(QString msg);
void sendBtnClicked();
void lineRetrunPressed();
void onInitConnect();
signals:
void sigSendMsg(QString);
private:
Ui::Widget *ui;
QRemoteObjectNode * m_pRemoteNode = nullptr;
QRemoteObjectDynamicReplica * m_pInterface = nullptr;
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include "ui_Widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
init();
}
Widget::~Widget()
{
delete ui;
}
void Widget::init()
{
setWindowTitle("This is Client");
ui->inputLine->setFocus();
m_pRemoteNode = new QRemoteObjectNode(this);
m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
m_pInterface=m_pRemoteNode->acquireDynamic("Interfaces1");//动态获取
//只有Replica初始化好了才能真正使用它,要不然connect无效
connect(m_pInterface, &QRemoteObjectDynamicReplica::initialized, this, &Widget::onInitConnect);
}
void Widget::onReceiveMsg(QString msg)
{
QDateTime curDateTime=QDateTime::currentDateTime();
QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit->append(temp+QString(" Server: ") + msg);
}
void Widget::sendBtnClicked()
{
QString msg = ui->inputLine->text();
if(!msg.isEmpty()){
emit sigSendMsg(msg);
}
QDateTime curDateTime=QDateTime::currentDateTime();
QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit->append(temp+QString(" Client: ") + msg);
ui->inputLine->clear();
}
void Widget::lineRetrunPressed()
{
sendBtnClicked();
}
void Widget::onInitConnect()
{
connect(m_pInterface,SIGNAL(sigMessage(QString)),this,SLOT(onReceiveMsg(QString)));
connect(this,SIGNAL(sigSendMsg(QString)),m_pInterface,SLOT(onMessage(QString)));
connect(ui->sendBtn,&QPushButton::clicked,this,&Widget::sendBtnClicked);
connect(ui->inputLine,&QLineEdit::returnPressed,this,&Widget::lineRetrunPressed);
}