当前位置:   article > 正文

Qt Remote Objects远程调用模块的基本使用_qremoteobjecthost

qremoteobjecthost

0.前言

Qt Remote Objects 是从 Qt5.9 加入的 RPC 远程调用模块,详情可以从文档中了解:

https://doc.qt.io/qt-5/qtremoteobjects-index.html

从个人使用实践来看,相比其他流行的 RPC 框架,也有一些优势:

  • Qt-like:如果使用 grpc,那么一些 Qt 自带的类型,如 QString 、QByteArray 等需要转换成 grpc 支持的几种基本类型,序列化时会有字符串编解码不一致的风险。使用 QtRO 则天生支持这些 Qt 的类型,而且定义的接口支持信号槽。
  • 轻便:QtRO 模块是一个轻量级的模块,使用简单,也不用再去编译第三方库。

也存在一些问题:

  • 功能太少:目前只支持一些元对象之上的接口定义,如属性、信号槽,没有流式接口,没有普通类成员或函数。
  • host 不能直接访问当前连接的 node,服务端是所有已连接的 node 共享的,如果 host-source 发信号,那么所有连接的 node 都会收到这个信号。从这点来看 QtRO 更适合单个客户端的进程交互,不适合多个客户端的并发访问,多个客户端时要独立操作则不该使用信号,可以通过槽函数返回值来返回结果。

QtRO 模块使用前先在 pro 文件加上 remoteobjects 启用该模块

QT += remoteobjects

有两种使用方式:1.Static Source,定义一个 rep 接口文件,分别生成服务端和客户端的接口定义;2.Dynamic Replica,服务端定义接口(用不用 rep 都可以),客户端连接上后动态获取元对象信息。

本文 Demo:

https://github.com/gongjianbo/MyTestCode/tree/master/Qt/QtRemoteObjects

1.Static Source(使用 rep 生成接口定义的方式)

基本使用流程为:写接口定义文件 rep,生成 replica(客户端) 和 source(服务端) 两套接口实现,在代码中使用生成的接口进行远程调用。

1.1.接口定义 rep 文件

接口定义参考官方文档:

https://doc.qt.io/qt-5/qtremoteobjects-repc.html

属性、信号槽的定义类似 QObject 子类中的定义方式。

这里分享几个我遇到的问题:

  • ENUM 得定义在 class 中,不然不能直接访问,因为它会在外面套一层 QObject 类。
  • POD 成员都是属性,如果想使用自定义的结构体不能直接写在 rep 中,需要 include 结构体头文件,这样只要信号槽支持的自定义类型就可以随便用了。
  • 使用自定义类型需要写比较运算符以支持如自动生成的属性 set 函数中进行比较,还要写 QDataStream 的输入输出序列化。

下面是我的 simple.rep 接口定义文件和 Define.h 结构体头文件:

  1. //rep文档https://doc.qt.io/qt-5/qtremoteobjects-repc.html
  2. //引入结构体定义的头文件
  3. #include "Define.h"
  4. //POD,类似结构体,但是成员都是属性
  5. POD MyPod(QString name, int age, bool sex)
  6. //MODEL和QAbstractItemModel相关,太鸡肋,略去
  7. //类
  8. class MyObject{
  9. //属性
  10. PROP(int value)
  11. }
  12. //会生成两种接口
  13. //[类名Replica]给客户端
  14. //[类名Source]和[类名SimpleSource]给服务端
  15. class Interface{
  16. //属性
  17. PROP(QString name="gongjianbo" READONLY)
  18. PROP(QString tag)
  19. //使用rep中定义的类作为属性
  20. CLASS subObject(MyObject)
  21. //信号
  22. SIGNAL(dataChanged(const QString &data))
  23. //槽函数,省略返回值则为void
  24. SLOT(void setData(const QString &data))
  25. SLOT(QString getData())
  26. //枚举,不放在class里会自动再套一层QObject类不方便访问
  27. ENUM MyEnum{
  28. EnumA = 0,
  29. EnumB
  30. }
  31. SLOT(void testEnum(MyEnum t))
  32. //使用自定义的结构体
  33. SLOT(void testStruct(MyStruct t))
  34. }
  1. #pragma once
  2. #include <QObject>
  3. #include <QDataStream>
  4. //支持使用自定义结构体,但是只能以include的方式,不能直接在rep中定义
  5. //由于自动生成的代码需要比较和序列化,所以要重载一些函数
  6. struct MyStruct
  7. {
  8. QString name;
  9. int age;
  10. bool sex;
  11. };
  12. Q_DECLARE_METATYPE(MyStruct)
  13. bool operator==(const MyStruct &left, const MyStruct &right);
  14. bool operator!=(const MyStruct &left, const MyStruct &right);
  15. //重载输入输出运算符,以支持datastream序列化自定义结构
  16. QDataStream& operator <<(QDataStream& out, const MyStruct& item);
  17. QDataStream& operator >>(QDataStream& in, MyStruct& item);

定义好之后在接口客户端引入:

REPC_REPLICA += $$PWD/simple.rep

构建后会在输出目录生成  rep_simple_replica.h,里面有实现了接口的 QRemoteObjectReplica 子类。

在接口服务端引入:

REPC_SOURCE += $$PWD/simple.rep

构建后会在输出目录生成  rep_simple_source.h,里面有实现了部分接口(槽需要继承重写)的一个接口类。

当然也可以用 REPC_MERGED 引入,会同时生成客户端和服务端的两套接口。

1.2.基本操作

生成接口类文件后,在客户端和服务端分别引入。服务端可能需要实现部分虚函数,客户端可以直接使用。

首先是客户端的基本操作(注意这个 Interface 是因为我 rep 接口定义取的名字叫 Interface):

  1. QRemoteObjectNode remoteNode;
  2. remoteNode.connectToNode(QUrl("local:qro_test"));
  3. InterfaceReplica *replica = remoteNode.acquire<InterfaceReplica>();
  4. //客户端可以判断连接与断开,服务端没有对应接口查询客户端列表
  5. //如果stateChanged直连槽函数调用接口,Qt5.15 mac上在拿返回值时会卡很久,可以使用QueuedConnection队列链接
  6. connect(replica,&InterfaceReplica::stateChanged,
  7. this,[this](QRemoteObjectReplica::State state, QRemoteObjectReplica::State oldState){
  8. },Qt::QueuedConnection);
  9. //连接服务端信号
  10. connect(replica,&InterfaceReplica::dataChanged,[]{});
  11. //调用服务端的接口
  12. if(replica->isReplicaValid())
  13. replica->setData("hello");

比较可惜的是客户端不能直接发信号,只能接收服务端过来的信号,以及调用槽函数。connetToNode 调用后如果服务端还没开,它会自动检测,也可以调用 replica 的 waitForSource 接口主动等。服务端重启后 node 会自动重连。

服务端的基本操作:

  1. QRemoteObjectHost host;
  2. MySource source; //继承生成的类并实现了部分虚函数
  3. host.setHostUrl(QUrl("local:qro_test"));
  4. host.enableRemoting(&source);
  5. //开启服务后可以发信号或者等被调用
  6. emit source.dataChanged("hello");

客户端可以判断连接与断开,服务端没有对应接口查询客户端列表,这点比较可惜。 

2.Dynamic Replica(动态获取接口信息的方式)

基本使用流程为:source(服务端)定义好接口,replica(客户端)连接成功后通过宏形式的信号槽,或者 QMetaObject::invokeMethod 来调用服务端接口。

相对于 Static Source,主要区别在于客户端没法直接访问服务端接口了,只能通过字符串的形式来调用。而服务端原本 rep 生成的就只是个 QObject 的子类,我们自己定义都可以。

  1. QRemoteObjectNode remoteNode;
  2. remoteNode.connectToNode(QUrl("local:qro_test"));
  3. //acquire的name,和host.enableRemoting的name配对
  4. QRemoteObjectDynamicReplica *replica = remoteNode.acquireDynamic("QRO");
  5. //客户端可以判断连接与断开,服务端没有对应接口查询客户端列表
  6. //如果stateChanged直连槽函数调用接口,Qt5.15 mac上在拿返回值时会卡很久,可以使用QueuedConnection队列链接
  7. connect(replica,&InterfaceReplica::stateChanged,
  8. this,[this](QRemoteObjectReplica::State state, QRemoteObjectReplica::State oldState){
  9. },Qt::QueuedConnection);
  10. //连接服务端信号槽,需要在连接完成后
  11. connect(replica,&QRemoteObjectDynamicReplica::initialized,
  12. [](){
  13. connect(replica, SIGNAL(serverSignal(const QString &)), this, SLOT(clientSlot(const QString &)));
  14. connect(this, SIGNAL(clientSignal(const QString &)), replica, SLOT(serverSlot(const QString &)));
  15. });
  16. //调用服务端的接口
  17. if(replica->isReplicaValid())
  18. QMetaObject::invokeMethod(replica, "setData", Qt::DirectConnection, Q_ARG(QString, "hello"));

3.参考

文档:https://doc.qt.io/qt-5/qtremoteobjects-gettingstarted.html

博客:https://zhuanlan.zhihu.com/p/347981991

博客:https://zhuanlan.zhihu.com/p/36501814

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/184735
推荐阅读
相关标签
  

闽ICP备14008679号