Qt Remote Object(QtRO)實現進程間通信

概述

Qt Remote Object簡稱QtRO,這是Qt5.9以後官方推出來的新模塊,專門用於進程間通信(IPC)。在這之前,要實現進程間通信有多種方式,這裏就不做介紹了,而Qt官方推出的這個新模塊是基於Socket來封裝的,使用起來非常方便,兼容LPC和RPC。LPC即Local Process Communication,而RPC是指Remote Process Communication,兩者都屬於IPC。QtRO能夠工作於這兩種不同的模式:如果用於LPC,則QtRO使用QLocalSocket;如果是用於RPC,則使用QTcpSocket。對於一個Qt開發者來說,如果項目中涉及到進程間通信,那麼直接使用現成的模塊進行開發, 莫過於是最好的選擇,集成度高,代碼量少。

QtRO

QtRO本質上是一個點對點的通信網絡。每個進程通過QRemoteObjectNode接入QtRO網絡。功能提供節點(可以理解爲服務器)需要使用QRemoteObjectHost將一個提供實際功能的QObject派生類註冊進QtRO網絡中,然後其他使用該功能的程序則通過各自的QRemoteObjectNode連接到該Host上,然後acquire一個該功能對象的Replica。等到該Replica初始化好後,該程序就能夠使用Replica中的信號、槽以及屬性,就好像功能類就在本地一樣。

關鍵步驟

要使用QtRO有幾個關鍵步驟,我們暫且將兩個端分爲Server和Client。

  • Server端需要把功能類通過QRemoteObjectHost的enableRemoting方法共享出來
  • Client連接到該QRemoteObjectHost,然後acquire到Replica
  • QtRO會自動初始化該Replica,待初始化完後客戶端就可以用該Replica

示例

QtRO有分兩種Replica,一種是靜態Replica,一種是動態Replica。我們這裏先來介紹靜態Replica使用方式。
直接通過示例來一步步進行講解。

本示例中,我們創建兩個進程,用於相互發送消息。

1.創建Server端

首先我們新建一個Server端工程,QtRoServer,爲了方便,直接用Qt Designer畫了一個簡單的消息發送窗口:

2.創建rep文件

rep文件是一種DSL(Domain Specific Language),專門用於定義QtRO接口。在編譯的時候,該文件會首先經過repc.exe這個程序處理,生成對應的頭文件和源文件。只要安裝Qt時選擇了Qt RemoteObjects模塊,repc.exe就在Qt安裝目錄的bin目錄中。
我們通過在rep文件中定義接口,用於QtRO中進行共享,關於rep文件的寫法具體可以看官方介紹

commoninterface.rep

class CommonInterface
{
    SIGNAL(sigMessage(QString msg))   //server下發消息給client
    SLOT(void onMessage(QString msg)) //server接收client的消息
}

注意,兩個要通信的進程必須使用同一個rep文件,要不然會無法建立連接。

3.編譯

注意,創建完rep文件後, 在server端的工程文件中將rep文件添加進來,如下:

REPC_SOURCE += \
    ../Reps/CommonInterface.rep

然後直接qmake,編譯,這時候repc.exe會將rep文件生成對應的頭文件,在程序輸出目錄下可以找到,如下:
在這裏插入圖片描述
其文件內容如下:

4.實現功能類

接着,我們需要繼承自動生成的這個類,並且實現其中的所有純虛函數:

頭文件

#ifndef COMMONINTERFACE_H
#define COMMONINTERFACE_H

#include "rep_commoninterface_source.h"

class CommonInterface : public CommonInterfaceSource
{
    Q_OBJECT
public:
    explicit CommonInterface(QObject * parent = nullptr);
    virtual void onMessage(QString msg);
    void sendMsg(const QString &msg);
signals:
    void sigReceiveMsg(const QString &msg);

};

#endif // COMMONINTERFACE_H

源文件

#include "commoninterface.h"
#include <QDebug>

CommonInterface::CommonInterface(QObject *parent):
    CommonInterfaceSource(parent)
{
}

/**
 * @brief CommonInterface::onMessage
 * @param msg
 * 接收客戶端的消息
 */
void CommonInterface::onMessage(QString msg)
{
    emit sigReceiveMsg(msg);
}

/**
 * @brief CommonInterface::sendMsg
 * @param msg
 * 發送消息給客戶端
 */
void CommonInterface::sendMsg(const QString &msg)
{
    emit sigMessage(msg);
}

5.實現Server端主邏輯

接下來實現Server端主窗口類邏輯,在其中初始化QtROM最關鍵的步驟:

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include "commoninterface.h"
#include <QRemoteObjectHost>

namespace Ui {
class MainWidget;
}

class MainWidget : public QWidget
{
    Q_OBJECT
public:
    explicit MainWidget(QWidget *parent = nullptr);
    ~MainWidget();

private slots:
    void on_pushButton_clicked();
    void onReceiveMsg(const QString &msg);

    void on_lineEdit_returnPressed();

private:
    void init();
private:
    Ui::MainWidget *ui;
    CommonInterface * m_pInterface = nullptr;
    QRemoteObjectHost * m_pHost = nullptr;
};

#endif // MAINWIDGET_H

mainwidget.cpp

#include "mainwidget.h"
#include "ui_mainwidget.h"

MainWidget::MainWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MainWidget)
{
    ui->setupUi(this);
    this->setWindowTitle("This is Server");
    init();
    ui->textEdit->setReadOnly(true);
}

MainWidget::~MainWidget()
{
    delete ui;
}

void MainWidget::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,&MainWidget::onReceiveMsg);
}

void MainWidget::on_pushButton_clicked()
{
    QString msg = ui->lineEdit->text();
    if(!msg.isEmpty()){
        m_pInterface->sendMsg(msg);
    }
    ui->textEdit->append(QString("Server:") + msg);
    ui->lineEdit->clear();
}

void MainWidget::onReceiveMsg(const QString &msg)
{
    ui->textEdit->append(QString("Client:") + msg);
}

void MainWidget::on_lineEdit_returnPressed()
{
    on_pushButton_clicked();
}

注意,這裏的

m_pHost->setHostUrl(QUrl("local:interfaces"));

我這裏是本機中不同進程的通信,可以HostURL中字符串格式爲"local:xxxx",其中xxxx必須是唯一的字符串,不同和其他程序有衝突,否則將會無法連接。
如果要實現局域網內不同機器間的通信,那麼需要將url改爲本機IP地址,比如QUrl("tcp://192.168.1.10:9999"),具體參考官方介紹
這裏的192.168.1.10一定要是server端主機的ip地址才行,端口號也必須唯一。

Ok,到這裏Server端程序就創建完成了,接下來看Client端。

Client端

同樣的,Client端也是和Server端一樣的界面,然後Client端和Server共用同一個rep文件,在工程文件pro中添加,如下 :

REPC_REPLICA += \
    ../Reps/CommonInterface.rep

注意,這裏和Server端添加方式不一樣,server端是REPC_SOURCE。

編譯

同樣, 在client添加完rep過後,直接編譯,然後會在輸出目錄生成一個文件:
在這裏插入圖片描述

和server端不同的是,client端不需要重新實現功能類,只需要在主窗口中連接server端即可。

實現Client端主邏輯

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QRemoteObjectNode>
#include "rep_CommonInterface_replica.h"

namespace Ui {
class MainWidget;
}

class MainWidget : public QWidget
{
    Q_OBJECT

public:
    explicit MainWidget(QWidget *parent = nullptr);
    ~MainWidget();

private slots:
    void onReceiveMsg(QString msg);
    void on_pushButton_clicked();

    void on_lineEdit_returnPressed();

private:
    void init();
private:
    Ui::MainWidget *ui;
    QRemoteObjectNode * m_pRemoteNode = nullptr;
    CommonInterfaceReplica * m_pInterface = nullptr;
};

#endif // MAINWIDGET_H

源文件

#include "mainwidget.h"
#include "ui_mainwidget.h"

MainWidget::MainWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MainWidget)
{
    ui->setupUi(this);
    this->setWindowTitle("This is Client");
    init();
    ui->textEdit->setReadOnly(true);
}

MainWidget::~MainWidget()
{
    delete ui;
}

void MainWidget::init()
{
    m_pRemoteNode = new QRemoteObjectNode(this);
    m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
    m_pInterface = m_pRemoteNode->acquire<CommonInterfaceReplica>();

    connect(m_pInterface,&CommonInterfaceReplica::sigMessage,
            this,&MainWidget::onReceiveMsg);
}

/**
 * @brief MainWidget::onReceiveMsg
 * @param msg
 * 接收服務器下發的消息
 */
void MainWidget::onReceiveMsg(QString msg)
{
    ui->textEdit->append(QString("Server:") + msg);
}

void MainWidget::on_pushButton_clicked()
{
    QString msg = ui->lineEdit->text();
    if(!msg.isEmpty()){
        m_pInterface->onMessage(msg); //調用槽發送消息給服務器
    }
    ui->textEdit->append(QString("Client:") + msg);
    ui->lineEdit->clear();
}

void MainWidget::on_lineEdit_returnPressed()
{
    on_pushButton_clicked();
}

同樣,m_pRemoteNode->connectToNode(QUrl("local:interfaces")); 這裏的連接地址和server的必須保持一致,然後通過acquire<CommonInterfaceReplica>(); 連接Server端。

ok,至此,Client已全部搞定。

運行效果

在這裏插入圖片描述

結語

這裏指介紹了靜態Replica的方式,還有一種是Dynamic Replica的方式,後面再介紹。
通過上述示例可以看到,QtRO使用非常簡單,代碼簡潔,直接通過信號槽的方式就可以輕鬆實現進程通信,無需自定義複雜的通信方式。

上述Demo下載地址

參考文章:
https://doc.qt.io/qt-5/qtremoteobjects-gettingstarted.html
https://zhuanlan.zhihu.com/p/36501814

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章