概述
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使用非常簡單,代碼簡潔,直接通過信號槽的方式就可以輕鬆實現進程通信,無需自定義複雜的通信方式。
參考文章:
https://doc.qt.io/qt-5/qtremoteobjects-gettingstarted.html
https://zhuanlan.zhihu.com/p/36501814