Qt Remote Object(QtRO)实现进程间通信
概述
Qt Remote Object簡稱QtRO,這是Qt5.9以后官方推出來的新模塊,專門用于進(jìn)程間通信(IPC)。在這之前,要實(shí)現(xiàn)進(jìn)程間通信有多種方式,這里就不做介紹了,而Qt官方推出的這個(gè)新模塊是基于Socket來封裝的,使用起來非常方便,兼容LPC和RPC。LPC即Local Process Communication,而RPC是指Remote Process Communication,兩者都屬于IPC。QtRO能夠工作于這兩種不同的模式:如果用于LPC,則QtRO使用QLocalSocket;如果是用于RPC,則使用QTcpSocket。對于一個(gè)Qt開發(fā)者來說,如果項(xiàng)目中涉及到進(jìn)程間通信,那么直接使用現(xiàn)成的模塊進(jìn)行開發(fā), 莫過于是最好的選擇,集成度高,代碼量少。
QtRO
QtRO本質(zhì)上是一個(gè)點(diǎn)對點(diǎn)的通信網(wǎng)絡(luò)。每個(gè)進(jìn)程通過QRemoteObjectNode接入QtRO網(wǎng)絡(luò)。功能提供節(jié)點(diǎn)(可以理解為服務(wù)器)需要使用QRemoteObjectHost將一個(gè)提供實(shí)際功能的QObject派生類注冊進(jìn)QtRO網(wǎng)絡(luò)中,然后其他使用該功能的程序則通過各自的QRemoteObjectNode連接到該Host上,然后acquire一個(gè)該功能對象的Replica。等到該Replica初始化好后,該程序就能夠使用Replica中的信號(hào)、槽以及屬性,就好像功能類就在本地一樣。
關(guān)鍵步驟
要使用QtRO有幾個(gè)關(guān)鍵步驟,我們暫且將兩個(gè)端分為Server和Client。
- Server端需要把功能類通過QRemoteObjectHost的enableRemoting方法共享出來
- Client連接到該QRemoteObjectHost,然后acquire到Replica
- QtRO會(huì)自動(dòng)初始化該Replica,待初始化完后客戶端就可以用該Replica
示例
QtRO有分兩種Replica,一種是靜態(tài)Replica,一種是動(dòng)態(tài)Replica。我們這里先來介紹靜態(tài)Replica使用方式。
直接通過示例來一步步進(jìn)行講解。
本示例中,我們創(chuàng)建兩個(gè)進(jìn)程,用于相互發(fā)送消息。
1.創(chuàng)建Server端
首先我們新建一個(gè)Server端工程,QtRoServer,為了方便,直接用Qt Designer畫了一個(gè)簡單的消息發(fā)送窗口:
2.創(chuàng)建rep文件
rep文件是一種DSL(Domain Specific Language),專門用于定義QtRO接口。在編譯的時(shí)候,該文件會(huì)首先經(jīng)過repc.exe這個(gè)程序處理,生成對應(yīng)的頭文件和源文件。只要安裝Qt時(shí)選擇了Qt RemoteObjects模塊,repc.exe就在Qt安裝目錄的bin目錄中。
我們通過在rep文件中定義接口,用于QtRO中進(jìn)行共享,關(guān)于rep文件的寫法具體可以看官方介紹
commoninterface.rep
class CommonInterface {SIGNAL(sigMessage(QString msg)) //server下發(fā)消息給clientSLOT(void onMessage(QString msg)) //server接收client的消息 }注意,兩個(gè)要通信的進(jìn)程必須使用同一個(gè)rep文件,要不然會(huì)無法建立連接。
3.編譯
注意,創(chuàng)建完rep文件后, 在server端的工程文件中將rep文件添加進(jìn)來,如下:
REPC_SOURCE += \../Reps/CommonInterface.rep接著添加QtRO模塊:
QT += remoteobjects然后直接qmake,編譯,這時(shí)候repc.exe會(huì)將rep文件生成對應(yīng)的頭文件,在程序輸出目錄下可以找到,如下:
其文件內(nèi)容如下:
4.實(shí)現(xiàn)功能類
接著,我們需要繼承自動(dòng)生成的這個(gè)類,并且實(shí)現(xiàn)其中的所有純虛函數(shù):
頭文件
#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* 發(fā)送消息給客戶端*/ void CommonInterface::sendMsg(const QString &msg) {emit sigMessage(msg); }5.實(shí)現(xiàn)Server端主邏輯
接下來實(shí)現(xiàn)Server端主窗口類邏輯,在其中初始化QtROM最關(guān)鍵的步驟:
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_Hmainwidget.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"));我這里是本機(jī)中不同進(jìn)程的通信,可以HostURL中字符串格式為"local:xxxx",其中xxxx必須是唯一的字符串,不同和其他程序有沖突,否則將會(huì)無法連接。
如果要實(shí)現(xiàn)局域網(wǎng)內(nèi)不同機(jī)器間的通信,那么需要將url改為本機(jī)IP地址,比如QUrl("tcp://192.168.1.10:9999"),具體參考官方介紹
這里的192.168.1.10一定要是server端主機(jī)的ip地址才行,端口號(hào)也必須唯一。
Ok,到這里Server端程序就創(chuàng)建完成了,接下來看Client端。
Client端
同樣的,Client端也是和Server端一樣的界面,然后Client端和Server共用同一個(gè)rep文件,在工程文件pro中添加,如下 :
REPC_REPLICA += \../Reps/CommonInterface.rep注意,這里和Server端添加方式不一樣,server端是REPC_SOURCE。
接著添加QtRO模塊:
QT += remoteobjects編譯
同樣, 在client添加完rep過后,直接編譯,然后會(huì)在輸出目錄生成一個(gè)文件:
和server端不同的是,client端不需要重新實(shí)現(xiàn)功能類,只需要在主窗口中連接server端即可。
實(shí)現(xiàn)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_OBJECTpublic: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* 接收服務(wù)器下發(fā)的消息*/ 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); //調(diào)用槽發(fā)送消息給服務(wù)器}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已全部搞定。
運(yùn)行效果
結(jié)語
這里指介紹了靜態(tài)Replica的方式,還有一種是Dynamic Replica的方式,后面再介紹。
通過上述示例可以看到,QtRO使用非常簡單,代碼簡潔,直接通過信號(hào)槽的方式就可以輕松實(shí)現(xiàn)進(jìn)程通信,無需自定義復(fù)雜的通信方式。
上述Demo下載地址
參考文章:
https://doc.qt.io/qt-5/qtremoteobjects-gettingstarted.html
https://zhuanlan.zhihu.com/p/36501814
總結(jié)
以上是生活随笔為你收集整理的Qt Remote Object(QtRO)实现进程间通信的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++11 Lambda表达式(匿名函数
- 下一篇: Qt Remote Object(QtR