QtRO(Qt Remote Objects)分布式对象远程通信

一、什么是QtRO

Qt Remote Objects(QRO)是Qt提供的一种用于实现远程对象通信的机制。
QtRO支持两种类型的通信:RPC(远程过程调用)和LPC(本地进程通信)。

RPC(远程过程调用)包括以下几种类型:
基于HTTP协议的RPC:例如Dubbo、Thrift等。
基于二进制协议的RPC:例如GRPC、Hetty等。
基于TCP协议的RPC:例如RMI、Remoting等。

LPC包括基于共享内存的通信和基于消息传递的通信。
总的来说,QtRO类似于平时的socket通信、串口通信、信号槽通信。它最大的特点是集合了这些通信的功能,使得远端通信能与本机通信一样使用信号槽的方式来收发信息

最大的优点可以说是免去了平时自己创建client端和server端时需要每次创建通信线程,解析协议,分拣数据。这里不同的数据可以直接用不同的信号槽来完成,远端通信就像是同个软件中对象之间的通信一样方便。

二、使用QtRO编写服务端

2.1首先需要编写rep文件,rep文件中包含了通信之间定义的接口。具体说明见官方文档。

以下是我的文件interface.rep

class Interface
{
    SIGNAL(sigMessage(QString msg))   //发送文本
    SIGNAL(sigPixmap(QByteArray pix)) //发送图片
    SIGNAL(sigFile(QByteArray data,QString fname)) //发送文件
    SLOT(void onMessage(QString msg)) 
    SLOT(void onPixmap(QByteArray pix))
    SLOT(void onFile(QByteArray data,QString fname))
}

2.2在pro文件中添加remoteobjects模块,添加rep文件

QT += remoteobjects
REPC_SOURCE += \
    interface.rep

2.3构建一次工程,在输出目录会找到rep_interface_source.h文件,把它复制到工程目录。在工程中新建一个CommonInterface类:

commoninterface.h

#ifndef COMMONINTERFACE_H
#define COMMONINTERFACE_H

#include "rep_interface_source.h"
class CommonInterface : public CommonInterfaceSource
{
    Q_OBJECT
public:
    explicit CommonInterface(QObject *parent = nullptr);
    virtual void onMessage(QString msg) override;
    virtual void onPixmap(QByteArray pix) override;
    virtual void onFile(QByteArray data,QString fname) override;
    void sendMsg(const QString &msg);
    void sendPixmap(QByteArray pix);
    void sendFile(QByteArray data,QString fname);

signals:
    void sigReceiveMsg(const QString &msg);
    void sigReceivePix(QByteArray pix);
    void sigReceiveFile(QByteArray data,QString fname);
};

#endif // COMMONINTERFACE_H

commoninterface.cpp

#include "commoninterface.h"

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

}

void CommonInterface::onMessage(QString msg)
{
    emit sigReceiveMsg(msg);
}

void CommonInterface::onPixmap(QByteArray pix)
{
    emit sigReceivePix(pix);
}

void CommonInterface::onFile(QByteArray data,QString fname)
{
    emit sigReceiveFile(data,fname);
}

void CommonInterface::sendMsg(const QString &msg)
{
    emit sigMessage(msg);
}

void CommonInterface::sendPixmap(QByteArray pix)
{
    emit sigPixmap(pix);
}

void CommonInterface::sendFile(QByteArray data,QString fname)
{
    emit sigFile(data,fname);
}

2.4主界面

主界面创建了三个按钮,分别用于发送文字、图片、文件。一个LineEdit(发送文字)、一个TextEdit(接收文字)、一个Label(接收图片)。

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include "commoninterface.h"
#include 
#include 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private slots:

    void on_pushButton_clicked();
    void onReceiveMsg(const QString &msg);
    void on_pushButton_2_clicked();
    void onReceivePix(QByteArray pix);
    void on_pushButton_3_clicked();
    void onReceiveFile(QByteArray data,QString fname);

private:
    Ui::Widget *ui;
    CommonInterface *m_pInterface = nullptr;
    QRemoteObjectHost *m_pHost = nullptr;

    void init();
};
#endif // WIDGET_H

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
#include 
#include 
#include 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("server");
    init();
}

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


void Widget::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 Widget::onReceiveMsg(const QString &msg)
{
    ui->textEdit->append(QString("Client:")+msg);
}

void Widget::init()
{
    m_pHost = new QRemoteObjectHost(this);
    //m_pHost->setHostUrl(QUrl("tcp://192.168.137.100:8081"));
    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(m_pInterface,&CommonInterface::sigReceivePix,this,&Widget::onReceivePix);
    connect(m_pInterface,&CommonInterface::sigReceiveFile,this,&Widget::onReceiveFile);
}

void Widget::on_pushButton_2_clicked()
{
    QString file = QFileDialog::getOpenFileName(this,"open","./","*.png *.jpg");
    if(file.isEmpty())
        return;
    QPixmap pix;
    pix.load(file,"png");
    if(pix.isNull())
        qDebug()<<"error";
    QByteArray ba;
    QBuffer bf(&ba);
    pix.save(&bf,"png");
    m_pInterface->sendPixmap(ba);
}

void Widget::onReceivePix(QByteArray pix)
{
    ui->textEdit->append("收到图片");
    qDebug()<<pix.size();
    QPixmap p;
    p.loadFromData(pix);
    ui->label->setPixmap(p);
}

void Widget::on_pushButton_3_clicked()
{
    QString file = QFileDialog::getOpenFileName(this,"open","./","*.*");
    if(file.isEmpty())
        return;
    QFile f(file);
    if(f.open(QIODevice::ReadOnly)){
        QByteArray ba = f.readAll();
        QFileInfo info(file);
        file = info.fileName();
        m_pInterface->sendFile(ba,file);
        f.close();
    }
}

void Widget::onReceiveFile(QByteArray data,QString fname)
{
    ui->textEdit->append("收到文件:"+fname);
    QFile file(fname);
    if(file.open(QIODevice::WriteOnly)){
        file.write(data);
        file.close();
    }
}

到此,服务端完成。

三、使用QtRO编写客户端

2.1rep文件是通用的,不用重复创建,这里直接添加到工程。

pro:

QT += remoteobjects
REPC_REPLICA += \
    interface.rep

注意!!!这里关键字变成了REPC_REPLICA和服务端的不一样!!所以到下一步以后生产的头文件名称也不一样!

2.2构建项目,在输出目录找到rep_interface_replica.h文件,放进工程。

2.3客户端中不需要再去创建CommonInterface类了,我们直接在主界面widget.h中编写。客户端和服务端的ui界面一样。

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include 
#include "rep_interface_replica.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();
    void onReceiveMsg(QString msg);
    void on_pushButton_2_clicked();
    void onReceivePix(QByteArray pix);
    void on_pushButton_3_clicked();
    void onReceiveFile(QByteArray ba,QString fname);

private:
    Ui::Widget *ui;
    QRemoteObjectNode *m_pRemoteNode = nullptr;
    InterfaceReplica *m_pInterface = nullptr;
    void init();
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include 
#include 
#include 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("Client");
    init();
}

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


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

void Widget::onReceiveMsg(QString msg)
{
    ui->textEdit->append("Server:"+msg);
}

void Widget::init()
{
    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(m_pInterface,&CommonInterfaceReplica::sigPixmap,this,&Widget::onReceivePix);
    connect(m_pInterface,&CommonInterfaceReplica::sigFile,this,&Widget::onReceiveFile);
}

void Widget::on_pushButton_2_clicked()
{
    QString file = QFileDialog::getOpenFileName(this,"open","./","*.png *.jpg");
    if(file.isEmpty())
        return;
    QPixmap pix;
    pix.load(file,"png");
    if(pix.isNull())
        qDebug()<<"error";
    QByteArray ba;
    QBuffer bf(&ba);
    pix.save(&bf,"png");
    m_pInterface->onPixmap(ba);
}

void Widget::onReceivePix(QByteArray pix)
{
    ui->textEdit->append("收到图片");
    qDebug()<<pix.size();
    QPixmap p;
    p.loadFromData(pix);
    ui->label->setPixmap(p);
}

void Widget::on_pushButton_3_clicked()
{
    QString file = QFileDialog::getOpenFileName(this,"open","./","*.*");
    if(file.isEmpty())
        return;
    QFile f(file);
    if(f.open(QIODevice::ReadOnly)){
        QByteArray ba = f.readAll();
        QFileInfo info(file);
        file = info.fileName();
        m_pInterface->onFile(ba,file);
        f.close();
    }
}

void Widget::onReceiveFile(QByteArray data,QString fname)
{
    ui->textEdit->append("收到文件:"+fname);
    QFile file(fname);
    if(file.open(QIODevice::WriteOnly)){
        file.write(data);
        file.close();
    }
}

到此,客户端和服务端都完成了,他们之间可以互相收发文字、图片、文件。

四、QtRO支持的参数类型

4.1 从2.1中我们可以知道,QtRO可以收发的数据类型由rep文件中定义的信号和槽决定的,那是不是可以定义任何数据类型,实现任何数据类型的收发呢?显然不是的,QRO允许发送的信号参数类型包括以下几种:

1.基本数据类型:如int、bool、char、float、double等。
2.Qt的核心类:如QString、QList、QMap等。
3.Qt的自定义类:只要这些类实现了序列化功能,就可以作为信号参数。

因此我的图片收发用的是QByteArray,而不是直接用QPixmap。

上述代码中连接方式是本机内通信,若要拓展为远端通信,可以把URL设置为如下格式:

m_pHost->setHostUrl(QUrl("tcp://192.168.137.100:8081"));

五、适合使用QtRO的场合

只有在服务端和客户端均用Qt开发的时候,适合使用QtRO方式。使用QtRO使得接口定义和实现更加方便。当已经有服务端程序,仅用Qt编写客户端时,就无法使用QtRO了。

你可能感兴趣的:(qt,分布式)