Qt Remote Objects
是Qt推出的进程间通信(IPC)模块,用于实现进程间或远程网络间通信,简化分布式系统的开发。通过抽象底层通信细节,让开发者能够像操作本地对象一样调用远程对象的属性和方法。
关键组成
源对象(Source) 在服务端定义实际对象,对外暴露数据和方法。例如,一个控制设备的类可能包含状态属性和控制指令方法。 副本对象(Replica) 客户端通过副本对象(Replica)与源对象(Source)交互。Replica
会将客户端的操作转发给服务端,并同步更新属性变化。 通信节点(Node) 服务端通过 QRemoteObjectHost
发布对象,客户端通过 QRemoteObjectNode
连接服务端并获取副本对象(Replica)。 关键特性
透明通信:客户端调用远程方法如同本地调用。 自动同步:源对象的属性变化会自动同步到所有客户端副本。 多协议支持:支持本地进程通信(local://
)、TCP 网络通信(tcp://
)等。 动态发现:可选注册中心(Registry)实现服务自动发现。 典型场景
多进程协作:例如 GUI 应用与后台服务分离。 分布式应用:主控程序与多个子进程共享配置数据。 跨设备控制:如通过 PC 控制嵌入式设备。 微服务架构:将功能模块拆分为独立进程,通过 QtRO 通信。 QtRO与RPC框架的功能比较
通信模式 Qt Remote Objects RPC(Remote Procedure Call) 多客户端 ✅ ✅ 单向调用 ✅(Slots) ✅ 发布-订阅 ✅(Signals) ❌ 双向流式 ❌ ✅ 状态同步 ✅ ❌
补充说明
Replica 编译器(repc)基于 API 定义文件(“rep”文件)生成 QObject 头文件。当 repc 处理这些文件时,repc 会生成 Source
头文件和 Replica
头文件。 Qt Remote Objects 支持静态 和动态副本 两种方式,两种方式的客户端代码编写方式有所不同。
虽然 Qt Remote Objects 支持通过网络共享任何 QObject 对象,但使用 repc 定义对象有几个优点:
虽然动态副本(DynamicReplicas)很有用,但使用起来更麻烦。在对象初始化之前,是不知道API的,并且使用 API 时需要通过元对象(QMetaObject)的方法进行字符串查找。 在编译时知道接口可以更容易发现编译时与运行时的问题。 rep 格式支持默认值,如果您无法确保在实例化副本对象(Replica)时源对象(Source)可用,则可以使用默认值。 简单示例# 静态方式# 定义接口# QtRO 静态方式需要先定义副本对象接口文件(.rep),文件语法可以参考Qt Remote Objects Compiler 。
这里我编写了一个简单的设备控制接口文件:
1
2
3
4
5
6
7
8
class DeviceController
{
ENUM Status { Ok , Error }; // 状态枚举
PROP ( Status status = Ok READONLY ); // 状态属性,只读
SLOT ( void controlDevice ( QString command )); // 槽函数
SLOT ( void uploadFile ( QString fileName , QByteArray fileData )); // 槽函数
SIGNAL ( fileReceived ( QString fileName )); // 信号
};
接下来需要把接口文件添加到项目中,项目分为服务端和客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
# qmake中的写法
# 添加 Qt Remote Objects 支持
QT += remoteobjects
# 服务端项目(生成源对象)
REPC_SOURCE += devicecontroller.rep
# 客户端项目(生成副本对象)
REPC_REPLICA += devicecontroller.rep
# 同时生成源对象和副本对象的头文件
REPC_MERGED = devicecontroller.rep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# cmake中的写法
set ( CMAKE_INCLUDE_CURRENT_DIR ON )
# 添加 Qt Remote Objects 支持
find_package ( Qt6 REQUIRED COMPONENTS Core RemoteObjects )
# 服务端项目(生成源对象)
qt_add_repc_sources ( ${ PROJECT_NAME }
devicecontroller.rep
)
# 客户端项目(生成副本对象)
qt_add_repc_replicas ( ${ PROJECT_NAME }
devicecontroller.rep
)
# 同时生成源对象和副本对象的头文件
qt_add_repc_merged ( ${ PROJECT_NAME }
devicecontroller.rep
)
添加完成后,构建一下项目,可以看到生成了源对象头文件rep_devicecontroller_source.h 和副本对象头文件rep_devicecontroller_replica.h 。
rep_devicecontroller_source.h
▼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#ifndef REP_DEVICECONTROLLER_SOURCE_H
#define REP_DEVICECONTROLLER_SOURCE_H
// This is an autogenerated file.
// Do not edit this file, any changes made will be lost the next time it is generated.
#include <QtCore/qobject.h>
#include <QtCore/qdatastream.h>
#include <QtCore/qvariant.h>
#include <QtCore/qmap.h>
#include <QtCore/qmetatype.h>
#include <QtRemoteObjects/qremoteobjectnode.h>
#include <QtRemoteObjects/qremoteobjectsource.h>
using namespace Qt :: Literals :: StringLiterals ;
class DeviceControllerSource : public QObject
{
Q_OBJECT
Q_CLASSINFO ( QCLASSINFO_REMOTEOBJECT_TYPE , "DeviceController" )
Q_CLASSINFO ( QCLASSINFO_REMOTEOBJECT_SIGNATURE , "3947cd1a592cb801ecdf88688ceb676821764dc3" )
Q_PROPERTY ( Status status READ status NOTIFY statusChanged )
public :
enum Status {
Ok = 0 ,
Error = 1 ,
};
Q_ENUM ( Status )
public :
explicit DeviceControllerSource ( QObject * parent = nullptr ) : QObject ( parent )
{
qRegisterMetaType < Status > ();
}
public :
~ DeviceControllerSource () override = default ;
virtual Status status () const = 0 ;
Q_SIGNALS :
void statusChanged ( DeviceControllerSource :: Status status );
void fileReceived ( QString fileName );
public Q_SLOTS :
virtual void controlDevice ( QString command ) = 0 ;
virtual void uploadFile ( QString fileName , QByteArray fileData ) = 0 ;
private :
friend class QT_PREPEND_NAMESPACE ( QRemoteObjectNode );
};
class DeviceControllerSimpleSource : public DeviceControllerSource
{
Q_OBJECT
public :
explicit DeviceControllerSimpleSource ( QObject * parent = nullptr ) : DeviceControllerSource ( parent )
, m_status ( Ok )
{}
public :
~ DeviceControllerSimpleSource () override = default ;
Status status () const override { return m_status ; }
protected :
virtual void setStatus ( Status status )
{
if ( status != m_status ) {
m_status = status ;
Q_EMIT statusChanged ( m_status );
}
}
private :
Status m_status ;
};
...
#endif // REP_DEVICECONTROLLER_SOURCE_H
服务端# 接下来编写服务端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef MYDEVICESERVICE_H
#define MYDEVICESERVICE_H
#include "rep_devicecontroller_source.h"
class MyDeviceService : public DeviceControllerSimpleSource
{
Q_OBJECT
public :
explicit MyDeviceService ( QObject * parent = Q_NULLPTR );
~ MyDeviceService ();
public Q_SLOTS :
virtual void controlDevice ( QString command ) Q_DECL_OVERRIDE ;
virtual void uploadFile ( QString fileName , QByteArray fileData ) Q_DECL_OVERRIDE ;
};
#endif // MYDEVICESERVICE_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "mydeviceservice.h"
#include <QDebug>
MyDeviceService :: MyDeviceService ( QObject * parent )
: DeviceControllerSimpleSource { parent }
{
qDebug () << __PRETTY_FUNCTION__ ;
}
MyDeviceService ::~ MyDeviceService ()
{
qDebug () << __PRETTY_FUNCTION__ ;
}
void MyDeviceService :: controlDevice ( QString command )
{
qDebug () << __PRETTY_FUNCTION__ << command ;
setStatus ( Error );
}
void MyDeviceService :: uploadFile ( QString fileName , QByteArray fileData )
{
qDebug () << __PRETTY_FUNCTION__ ;
qDebug () << "Receiving file:" << fileName
<< " \n Content: \n " << QString :: fromUtf8 ( fileData );
setStatus ( Ok );
Q_EMIT fileReceived ( fileName );
}
启动服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <QCoreApplication>
#include "mydeviceservice.h"
int main ( int argc , char * argv [])
{
QCoreApplication a ( argc , argv );
QRemoteObjectHost host ;
// 进程间通信(IPC)
// host.setHostUrl(QUrl("local:devicecontroller"));
// 远程过程调用(RPC)
host . setHostUrl ( QUrl ( "tcp://0.0.0.0:2333" ));
MyDeviceService service ;
/**
* 第一个参数是源对象指针
* 第二个参数是源对象名字,客户端可根据名字获取源对象,
* 使用动态副本的方式时,必须填写源对象名字。
*/
host . enableRemoting ( & service , "mydeviceservice" );
return a . exec ();
}
客户端# 客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef MYDEVICECONTROLLER_H
#define MYDEVICECONTROLLER_H
#include <QObject>
#include <QPointer>
QT_BEGIN_NAMESPACE
class QRemoteObjectNode ;
QT_END_NAMESPACE
class MyDeviceController : public QObject
{
Q_OBJECT
public :
explicit MyDeviceController ( QObject * parent = Q_NULLPTR );
Q_INVOKABLE void test ( const QUrl & url );
private :
QPointer < QRemoteObjectNode > node ;
// QPointer<DeviceControllerReplica> replica;
};
#endif // MYDEVICECONTROLLER_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "mydevicecontroller.h"
#include "rep_devicecontroller_replica.h"
MyDeviceController :: MyDeviceController ( QObject * parent )
: QObject { parent }
, node { new QRemoteObjectNode ( this )}
{}
void MyDeviceController :: test ( const QUrl & url )
{
node -> connectToNode ( url );
qDebug () << "connected to:" << url ;
auto * replica = node -> acquire < DeviceControllerReplica > ( "mydeviceservice" );
connect ( replica , & DeviceControllerReplica :: statusChanged , this , [ & ]( DeviceControllerReplica :: Status s ) {
qDebug () << "Device status changed to:" << s ;
});
connect ( replica , & DeviceControllerReplica :: fileReceived , this , [ & ]( QString fileName ) {
qDebug () << "File sent. file -" << fileName ;
});
connect ( replica , & DeviceControllerReplica :: stateChanged , this ,
[ this ]( QRemoteObjectReplica :: State state , QRemoteObjectReplica :: State oldState ) {
qDebug () << "replica state changed:" << oldState << "→" << state ;
// 副本对象状态可用时才能调用接口
if ( state == QRemoteObjectReplica :: Valid ) {
DeviceControllerReplica * replica = qobject_cast < DeviceControllerReplica *> ( sender ());
Q_ASSERT ( replica );
replica -> controlDevice ( "echo" );
replica -> uploadFile ( "test.txt" , QString ( "你好!世界。" ). toUtf8 ());
}
});
}
客户端调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <QCoreApplication>
#include <QUrl>
#include "mydevicecontroller.h"
int main ( int argc , char * argv [])
{
QCoreApplication a ( argc , argv );
MyDeviceController client ;
// 进程间通信(IPC)
// client.test(QUrl("local:devicecontroller"));
// 远程过程调用(RPC)
client . test ( QUrl ( "tcp://192.168.1.3:2333" ));
return a . exec ();
}
动态副本# 动态副本的客户端代码有所不同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef MYDEVICECONTROLLER_H
#define MYDEVICECONTROLLER_H
#include <QObject>
#include <QPointer>
QT_BEGIN_NAMESPACE
class QRemoteObjectNode ;
QT_END_NAMESPACE
class MyDeviceController : public QObject
{
Q_OBJECT
public :
explicit MyDeviceController ( QObject * parent = Q_NULLPTR );
Q_INVOKABLE void test ( const QUrl & url );
Q_SLOT void onStatusChanged ( int );
Q_SLOT void onFileReceived ( const QString & );
private :
QPointer < QRemoteObjectNode > node ;
};
#endif // MYDEVICECONTROLLER_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "mydevicecontroller.h"
#include "rep_devicecontroller_replica.h"
MyDeviceController :: MyDeviceController ( QObject * parent )
: QObject { parent }
, node { new QRemoteObjectNode ( this )}
{}
void MyDeviceController :: test ( const QUrl & url )
{
node -> connectToNode ( url );
qDebug () << "connected to:" << url ;
auto * replica = node -> acquireDynamic ( "mydeviceservice" );
connect ( replica , & QRemoteObjectDynamicReplica :: stateChanged , this ,
[ this ]( QRemoteObjectReplica :: State state , QRemoteObjectReplica :: State oldState ) {
qDebug () << "replica state changed:" << oldState << "→" << state ;
if ( state == QRemoteObjectReplica :: Valid ) {
QRemoteObjectDynamicReplica * replica = qobject_cast < QRemoteObjectDynamicReplica *> ( sender ());
Q_ASSERT ( replica );
/** 由于使用动态副本方式,在副本初始化完成前,无法预先知道API,
* 只有在副本对象状态可用时,才能进行信号-槽连接。
* 并且无法使用自定义类型参数,如之前定义的枚举。
*/
// connect(replica, SIGNAL(statusChanged(int)), this, SLOT(onStatusChanged(int)));
connect ( replica , SIGNAL ( fileReceived ( QString )), this , SLOT ( onFileReceived ( QString )));
/** 调用接口需要使用元对象的`invokeMethod`方法
*/
QMetaObject :: invokeMethod ( replica , "controlDevice" , Q_ARG ( QString , "echo" ));
QMetaObject :: invokeMethod ( replica , "uploadFile" , Q_ARG ( QString , "test.txt" ),
Q_ARG ( QByteArray , QString ( "你好!世界。" ). toUtf8 ()));
}
});
}
void MyDeviceController :: onStatusChanged ( int s )
{
qDebug () << "Device status changed to:" << s ;
}
void MyDeviceController :: onFileReceived ( const QString & fileName )
{
qDebug () << "File sent. file -" << fileName ;
}
注意事项# Qt Remote Objects 使用内部协议在进程和/或设备之间传递数据。所有部分都需要使用相同的协议版本:如果版本不匹配,连接节点将输出警告,主机节点将不发送任何数据。
目前发布的版本:
协议版本 Qt版本 <1.2 在 QtRO 的技术预览阶段使用。 1.2 5.12.0 1.3 5.12.4 2.0 6.2.0
See Qt Remote Objects Protocol Versioning .
参考