简介
Qt Remote Object简称QtRO,这是Qt5.9以后官方推出来的新模块,专门用于进程间通信(IPC)。是基于Socket来封装的,兼容LPC和RPC。LPC即Local Process Communication,而RPC是指Remote Process Communication,两者都属于IPC。如果用于LPC,则QtRO使用QLocalSocket;如果是用于RPC,则使用QTcpSocket。
它最大的特点是使得远端通信能与本机通信一样使用信号槽的方式来收发信息。
每个进程通过QRemoteObjectNode接入QtRO网络。功能提供节点(可以理解为服务器)需要使用QRemoteObjectHost将一个提供实际功能的QObject派生类注册进QtRO网络中,然后其他使用该功能的程序则通过各自的QRemoteObjectNode连接到该Host上,然后acquire一个该功能对象的Replica。等到该Replica初始化好后,该程序就能够使用Replica中的信号、槽以及属性,就好像功能类就在本地一样。
优点:
- 使用 QtRO 则天生支持Qt 自带的类型,如 QString 、QByteArray 。
- 而且定义的接口支持信号槽。
缺点:
host 不能直接访问当前连接的 node,服务端是所有已连接的 node 共享的,如果 host-source 发信号,那么所有连接的 node 都会收到这个信号。从这点来看 QtRO 更适合单个客户端的进程交互,不适合多个客户端的并发访问,多个客户端时要独立操作则不该使用信号,可以通过槽函数返回值来返回结果。
关键步骤
要使用QtRO有几个关键步骤,我们暂且将两个端分为Server和Client。
Server端需要把功能类通过QRemoteObjectHost的enableRemoting方法共享出来
Client连接到该QRemoteObjectHost,然后acquire到Replica
QtRO会自动初始化该Replica,待初始化完后客户端就可以用该Replica。
QtRO支持的参数类型
QtRO可以收发的数据类型由rep文件中定义的信号和槽决定的,QRO允许发送的信号参数类型包括以下几种:
1.基本数据类型:如int、bool、char、float、double等。
2.Qt的核心类:如QString、QList、QMap等。
3.Qt的自定义类:只要这些类实现了序列化功能,就可以作为信号参数。
使用QtRO编写服务端
创建rep文件
rep文件是一种DSL(Domain Specific Language),专门用于定义QtRO接口。在编译的时候,该文件会首先经过repc.exe这个程序处理,生成对应的头文件和源文件。只要安装Qt时选择了Qt RemoteObjects模块,repc.exe就在Qt安装目录的bin目录中。
如 rep文件
#include <QObject>#include <QString>POD VarInfo(QString varName,QString value,);ENUM PlatType{P_Unknown = -1, //未知平台P_Firm = 0,P_Fit = 1,P_Speed = 2};class CommonInterface{SIGNAL(sigMessage(VarInfo msg)); //server下发消息给clientSLOT(bool onMessage(QString msg,PlatTypeEnum::PlatType type)); //server接收client的消息PROP(QString strname); // Property}
Rep 文件的介绍见:Qt Remote Objects Compiler | Qt Remote Objects 5.15.16
PROP
Q_PROPERTY元素是通过在rep文件中使用PROP关键字创建的。语法是PROP关键字后跟括号中的定义,其中定义是类型、名称和(可选的)默认值或属性。
PROP(QString strname);
CLASS
CLASS关键字为从QObject派生的对象生成特殊的Q_PROPERTY元素。这些属性与SOURCEONLYSETTER具有相同的语义。语法是CLASS关键字后跟属性名,然后是括在括号中的子对象类型。
Signal
Signal方法是通过使用rep文件中的SIGNAL关键字创建的。 用法是声明SIGNAL,后跟用括号括起来的所需签名。应该跳过void返回值。
SIGNAL(sigMessage(VarInfo msg));
SLOT
插槽方法是使用rep文件中的Slot关键字创建的。 用法是声明SLOT,后跟用括号括起来的所需签名。返回值可以包含在声明中。如果返回值被跳过,将在生成的文件中使用void。
SLOT(bool onMessage(QString msg,PlatTypeEnum::PlatType type));
ENUM
枚举(在QtRO中使用C++ enum和Qt的Q_enum的组合)是使用ENUM关键字描述的。
ENUM PlatType{
P_Unknown = -1, //未知平台
P_Firm = 0,
P_Fit = 1,
P_Speed = 2
};
POD
Plain Old Data 普通旧数据(POD)是一个描述简单数据集合的术语,类似于C++结构.即自定义结果类型。
POD VarInfo(
QString varName,
QString value,
);
REPC_SOURCE
指定项目中用于生成源文件的所有表示文件的名称。即用在服务端
REPC_SOURCE += \
../Reps/commoninterface.rep
REPC_REPLICA
指定项目中用于生成副本头文件的所有rep文件的名称,即用在客户端。
REPC_REPLICA += \
../Reps/commoninterface.rep
配置rep文件
在server端的pro文件中将rep文件添加进来
REPC_SOURCE += \
../Reps/commoninterface.rep
接着添加QtRO模块
QT += remoteobjects
直接qmake,编译,这时候repc.exe会将rep文件生成对应的头文件,在程序输出目录下可以找到.
打开文件如下图:
继承实现功能
继承自动生成的这个CommonInterfaceSimpleSource类,并且实现其中的所有纯虚函数。如果没有 加上PROP(QString strname); 就不会生成CommonInterfaceSimpleSource类,直接继承CommonInterfaceSource类。
头文件
#ifndef COMMONINTERFACE_H#define COMMONINTERFACE_H#include "rep_commoninterface_source.h" //在这里引用的是debug目录下编译的rep_commoninterface_source.hclass CommonInterface : public CommonInterfaceSimpleSource{Q_OBJECTpublic:explicit CommonInterface(QObject * parent = nullptr);//这个就是rep文件设置,接收数据的虚函数virtual bool onMessage(QString msg, PlatTypeEnum::PlatType type);void senddata(QString msg);signals:void sigReceiveMsg(QString msg);//把从客户端接收到的数据发送到界面上};#endif // COMMONINTERFACE_H
源文件
#include "CommonInterface.h"CommonInterface::CommonInterface(QObject *parent){}bool CommonInterface::onMessage(QString msg, PlatTypeEnum::PlatType type){if(type == PlatTypeEnum::PlatType::P_Firm){emit sigReceiveMsg(msg);}return true;}void CommonInterface::senddata(QString msg){VarInfo info;info.setValue(msg);info.setVarName("1");emit sigMessage(info);}
初始化QtRO并调用
头文件
#ifndef DIALOG_H#define DIALOG_H#include <QDialog>#include "CommonInterface.h"QT_BEGIN_NAMESPACEnamespace Ui { class Dialog; }QT_END_NAMESPACEclass Dialog : public QDialog{Q_OBJECTpublic:Dialog(QWidget *parent = nullptr);~Dialog();void init();private slots:void onReceiveMsg(QString msg);void on_pushButton_clicked();private:Ui::Dialog *ui;CommonInterface * m_pInterface ;QRemoteObjectHost * m_pHost ;};#endif // DIALOG_H
源文件
#include "dialog.h"#include "ui_dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){init();ui->setupUi(this);setWindowTitle("Server");}Dialog::~Dialog(){delete ui;}void Dialog::init(){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,&Dialog::onReceiveMsg);}void Dialog::onReceiveMsg(QString msg){ui->textEdit->clear();ui->textEdit->setText(msg);}void Dialog::on_pushButton_clicked(){QString text = ui->lineEdit->text();m_pInterface->senddata(text);}
这里是本机中不同进程的通信,可以HostURL中字符串格式为"local:xxxx",其中xxxx必须是唯一的字符串,不同和其他程序有冲突,否则将会无法连接。
使用QtRO编写客户端
配置rep文件
Client端和Server必须共用同一个rep文件,在工程文件pro中添加
REPC_REPLICA += \
../Reps/commoninterface.rep
添加QtRO模块
QT += remoteobjects
client添加完rep过后,直接编译,然后会在输出目录生成一个文件
打开文件:
初始化QtRO并调用
和server端不同的是,client端不需要重新实现功能类。直接初始化并调用即可。
头文件
#ifndef DIALOG_H#define DIALOG_H#include <QDialog>#include "rep_CommonInterface_replica.h"QT_BEGIN_NAMESPACEnamespace Ui { class Dialog; }QT_END_NAMESPACEclass Dialog : public QDialog{Q_OBJECTpublic:Dialog(QWidget *parent = nullptr);~Dialog();private:void init();private slots:void on_pushButton_clicked();void onReceiveMsg(VarInfo msg);private:Ui::Dialog *ui;QRemoteObjectNode * m_pRemoteNode ;CommonInterfaceReplica * m_pInterface ;};#endif // DIALOG_H
源文件
#include "dialog.h"#include "ui_dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){init();ui->setupUi(this);setWindowTitle("Client");}Dialog::~Dialog(){delete ui;}void Dialog::init(){m_pRemoteNode = new QRemoteObjectNode(this);m_pRemoteNode->connectToNode(QUrl("local:interfaces"));m_pInterface = m_pRemoteNode->acquire<CommonInterfaceReplica>();connect(m_pInterface,&CommonInterfaceReplica::sigMessage,this,&Dialog::onReceiveMsg);}void Dialog::on_pushButton_clicked(){QString msg = ui->lineEdit->text();QRemoteObjectPendingReply<bool> ret = m_pInterface->onMessage(msg,PlatTypeEnum::PlatType::P_Firm); //异步调用槽发送消息给服务器bool bret = ret.waitForFinished();//等待函数if(bret){bool bval = ret.returnValue();ui->lineEdit->clear();}else{QString err = "超时";}}void Dialog::onReceiveMsg(VarInfo msg){ui->textEdit->clear();ui->textEdit->setText(msg.varName() + ":" + msg.value());}
注意:在客户端调用服务端的槽函数(也是虚函数,通过rep文件设置),属于异步调用。如果该槽函数有返回值的,一般要通过等待函数waitForFinished,并通过QRemoteObjectPendingReply类的returnValue获取返回值。