Qt Remote Object实现进程间通信

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);
}

运行截图

在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt共享内存是一种实现进程间通信的机制。进程间通信通常用于在不同的进程之间交换数据。共享内存就是将一块内存空间映射到多个进程的地址空间中,使得多个进程可以直接访问这块内存空间。Qt提供了QSharedMemory类来实现共享内存。 使用Qt共享内存进行进程间通信的步骤如下: 1. 创建一个QSharedMemory对象,并指定共享内存的唯一标识符。 2. 调用create()函数来创建共享内存,如果共享内存已经存在,则直接打开。 3. 调用attach()函数将共享内存映射到当前进程的地址空间中。 4. 使用writeData()函数向共享内存中写入数据。 5. 使用readData()函数从共享内存中读取数据。 6. 使用detach()函数将共享内存从当前进程的地址空间中解除映射。 7. 使用destroy()函数销毁共享内存。 通过这种方式,多个进程可以通过共享内存来交换数据,而无需通过消息传递等方式进行繁琐的数据传输。这样可以提高进程间通信的效率,减少系统资源的占用。 需要注意的是,共享内存的使用需要保证互斥性,避免多个进程同时对共享内存进行写操作导致数据混乱。可以使用QMutex等同步机制来解决这个问题。 总之,Qt共享内存提供了一种高效的进程间通信方式,使得多个进程可以方便地进行数据交换。可以广泛应用于需要实现进程间数据共享的场景,如多进程协同处理、进程间数据传递等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值