Qt新的远程通信机制简介:Qt Remote Objects (QtRO)



  • 0_1524119285345_222.jpg
    Qt新的远程通信机制简介:Qt Remote Objects (QtRO)

    从5.10开始加入。QtRO是一种进程间通信机制(IPC)。通过socket,它能实现进程间甚至是不同计算机之间的远程对象交互功能。QtRO扩展了Qt原有的功能,使得远程通信更加方便。

    接下来是QtRO的简单介绍,对各个部分进行概述。

    QtRO封装了序列化和反序列化过程。用户可以假装有一个对象可供访问,而实际上这个对象存在于另一个进程,甚至另一台计算机中。
    QtRO最大的特点,是它是基于现有的Qt对象API机制(使用Q_PROPERTY, signals以及slots定义的API)。即,有几个默认前提:
    1、远程的对象必须继承自QObject
    2、使用meta-object的机制(包括property、信号和槽)进行互动

    说一句题外话,其实Qt在很多交互场合都使用了这套机制,比如QML Engine和C++交互,JS Engine和C++交互,Web Engine和C++互动,我猜今年推出的Python Engine和C++交互也会使用MOC。

    互动的进程称为Node,每个Node都是对等的。Node上可以包含真正的对象,称作Source;Node也可以包含一个镜像对象,称作Replica。多个Replica可以连接到一个Source,当Source的属性发生变化,都会通知每个Replica。反过来,Replica可以调用函数来改变Source的属性。(每个Node都是对等的,可以既包含Source又包含Replica)

    Replica是一个轻量级的代理(映射Source的状态),同时也是一个继承自QObject的对象,因此可以与本地对象连接信号和槽。也就是说,Replica可以作为信号和槽的转发器,用户使用时就像对待本地对象一样。
    除了Source的映射外,Replica还具备一些属性,用来判断连接状态和是否初始完成。
    为了最大程度节省流量,Replica和Source的通信是基于事件机制。因此在初始时需要对所有属性进行初始化同步。

    QtRO的Source设计时有主动推送变化的功能,而不需要客户端去查询并等待(传统RPC机制)。相对来说更省流量。它和传统RPC有很明显的区别:
    1、双向通信,基于事件机制
    2、完全面向对象而不是函数

    接下来是技术细节介绍。(如果有兴趣可以往下看,内容有些枯燥,也可以参考官方的手册)

    Replica的使用。有两种方法可以获得Replica:
    1、编译时加入
    编译时加入的Replica继承自QRemoteObjectReplica。REPC生成器会自动生成Replica的定义文件(一个头文件)。在pro文件中加入REPC_REPLICA宏可以把生成过程集成到项目编译过程中去。Replica是一个完整的类型(不是虚类),但是没有公开的构造函数,用户需要使用QRemoteObjectNode::acquire模板函数来实例化Replica。
    2、动态获取
    动态获取的Replica继承自QRemoteObjectDynamicReplica。它由非模板函数QRemoteObjectNode::acquire()生成,必须提供一个Source名称。动态Replica使用起来可能有点罗嗦,但是不需要编译时的头文件。并且在QML或者一些脚本语言中使用起来更方便。动态Replica不支持初始化值,在初始化完成前也不能使用。
    以上两种实现方式的一个很重要的区别,就在于未初始化时的状态。因为一个动态Replica只有在初始化后才能获取metaObject,因此在初始化前它基本上没有API可用。反过来,编译时加入的Replica已经具备metaObject,因此在未初始化前它已经有完整的API。用户甚至可以指定属性的默认值,这些默认值可以一直用到Replica被远程Source初始化。

    Source是一个对象。它挂载到Node上,包含有公开API。
    你可以选择使用一个QObject的类直接作为Source,也可以选择在一个.rep文件中定义需要的API,交由REPC生成器生成模板。
    如果你已经有一个完整的QObject类,可以把它传给QRemoeObjectHostBase:: enableRemoting()函数,让它成为一个Source。
    你可以让REPC生成器为你生成Source,即一个定义了API的头文件(在pro文件中使用REPC_SOURCE宏)。有三个选择去实现这些API(假设你的Source类的名字叫做Foo):
    1、继承FooSimpleSource
    在头文件里,定义了一个<Type>SimpleSource类。它给每个属性提供最基本的getter/ setter方法。这里的<Type>代表了.rep文件中Source类名称。即如果.rep文件中定义的类名称叫做“MyType”,那么在生成的头文件中就会有一个MyTypeSimpleSource的类。最简单快速的方法就是继承这个类,并且实现这个类中的API(都是纯虚函数)。你也可以Override任何需要的属性和函数。
    2、继承FooSource
    如果你需要隐藏实现的细节,那么你可以使用<Type>Source类。在生成的头文件中,它是第二个定义的类。这个类的定义没有包含任何数据对象,getter/setting函数也变成了纯虚函数。继承该类,用户有更多的灵活性去实现这个类,当然用户要写更多的代码。
    3、在用户自己的QObject对象里实现FooSourceAPI
    最后这个办法,<Type>SourceAPI类是一个模板类,专门给QRemoeObjectHostBase:: enableRemoting()的模板函数使用。它让你可以使用任意QObject子类(必须含有API)作为Source。如果这个类没有提供正确的API,你会收到编译时的警告。使用这个类让你可以隐藏或变换属性或信号槽。

    在每个端点可以存在许多Source,因此每个Source需要一个独一无二的名称。所有的REPC生成的头文件都有一个方式来确定名称(Q_CLASSINFO宏,用于replica/ simplesource/source,对于SourceAPI使用一个静态的name()函数)。如果你使用自己的QObject子类作为Source(使用QRemoteObjectHostBase::enableRemoting),那么Source的名字使用如下逻辑来决定:
    1、如果该类或者任何它的父类的Q_CLASSINFO序列中定义过RemoteObjectType,那么QRemoteObjectHostBase::enableRemoting中传入的name参数将被使用。
    2、否则,QObject的objectName被使用
    3、如果以上都失败,QRemoteObjectHostBase::enableRemoting返回错误。

    注意:QObject层面的API不会同步给Replica。比如,Replica有一个destroyed信号,它不会触发source的destroyed信号,反之亦然。因为Source和每个Replica都是一个独立的QObject。同步的API在.rep模板文件中定义,或者对于直接使用的QObject的情况,同步的API是在QObject的继承链上的元素。除非在某个父类中定义了Q_CLASSINFO("RemoteObject Type")。如果定义过Q_CLASSINFO("RemoteObject Type")那么那个父类是API检索中最低层。

    Node的使用。Node是进程间通信的平台和基础。所有QtRO的交互数据都被Node封装成尽可能小的独立数据包。
    每个使用QtRO的进程都必须实例化一种Node类(可以是QRemoteObjectNode,QRemoteObjectHost或者是QRemoteObjecRegistryHost)。后面几种Node提供了更多功能。QRemoteObjectHost和QRemoteObjecRegistryHost都支持enableRemoting()/disableRemote()函数,用来把Source对象发布到网络中去。如果要使用Registry功能,网络上必须要有一个QRemoteObjectRegistryHost的Node。然后其他的Node可以传递RegistryHost的URL参数到Node的registryAddress构造函数,或者传送URL参数到setRegistryUrl()函数。
    QtRO是P2P的网络。为了acquire()一个有效的Replica,承载Replica的Node必须连接到带Source的Node。Host Node允许其他Node通过设置独一无二的地址连接到它(地址传递到其他Node的构造函数,或者是使用setHostUrl方法)。Replica所在的Node必须建立与Host Node的连接,才能初始化Replica并保持数据一致。

    使用QtRO的URL连接Node
    Host Node使用用户自定义的URL来简化连接。目前QtRO支持两种连接方式(很有可以会扩展)。一个是使用标准TCP/IP的连接,它即支持同一台设备不同进程间通信,也支持不同设备通信。另一种使用local连接,它协议简单,具体实现依赖于OS特性。但不支持设备间通信。
    使用local连接时,必须使用一个独一无二的名字。使用TCP连接时,必须使用一个独一无二的IP和Port的组合。
    目前QtRO不支持zeroconf(零配置网络规范)。因此所有Node必须事前直销所有连接配置。QRemoteObjectRegistry用来简化连接过程,特别是对与有多个Host Node的网络。
    连接类型总结如下表:

    URL Host Node Connecting Node
    QUrl("local:replica") QLocalServer("replica") QLocalSocket("replica")
    QUrl("tcp://192.168.1.1:9999") QTcpServer("192.168.1.1",9999) QTcpSocket("192.168.1.1",9999)

    Node有多个enableRemoting()函数,用来在网络上共享对象(如果Node不是Host Node则会报错)。其他设备/进程,如果需要与其交互,可以使用Node的acquire()函数来实例化Replica。



  • 首发最新模块的介绍!很棒啊。强烈支持一个!而且我觉得这个技术,和微软的COM组件对象编程很相似,值得我们去推广使用!🤑 😛 😛



  • 没写完呢。
    这个对Qter非常有用。
    其他平台其实都有类似实现,其实Qt平台上已经有点晚了。



  • @linbin823 这种情况是晚了一些,大家都在使用COM技术,还有各种脚本语言的实现。现在推广来说稍微难了。



  • 期待下文,最好能做个Demo



  • 这东西是不是相当于java的RMI?都是十几年前的东西了,为什么Qt要到现在才开始提供?而且我记得我面试Java工作的时候,RMI和序列化数据也是一个重点,Qt就一点感觉都没有吗?

    我还有一个疑惑很多年了,从学Qt的时候一直到现在:就是J2SE=Qt桌面版,J2ME=Qt嵌入式,但是J2EE为什么在Qt里没有相应的东西?Java真正能火起来其实是靠J2EE,后来又攀上了Android这颗大树。但是桌面版和嵌入式,绝对是Qt完胜。



  • @stlcours 这也不是什么很复杂的东西。QtRO解决的是远程信号槽以及属性的传递。都是非常上层的封装。

    Qt不做服务端,有好多技术和历史因素,毕竟它一路走来都是定位客户端和界面的开发框架。

    当然github上也有用Qt开发的服务端软件,有些benchmark分数还挺高。这个就只能说有需求就去玩玩,但估计成不了主流。



  • @linbin823 能举几个例子吗?有哪些Qt开发的服务端软件?是不是主流不要紧,关键是给我们这些人使用方便啊。



  • @stlcours
    做httpserver:tufao,cutelyst
    这两天还看到一个https://github.com/hgoldfish/qtnetworkng



  • 谢谢Linbin。虽然qtnetworkng是全英文文档,但是例子居然是"https://news.163.com,看来是国人的作品啊!!


Log in to reply
 

走马观花

最近的回复

  • Z

    我按照楼主说的,除了windeployqt以外,还需要Qt\labs\folderlistmodel,QtQuick\Layouts,QtQuick\VirtualKeyboard,QtQuick.2,platforminputcontexts这么几个目录,另外Qt5Qml.dll, Qt5Quick.dll也要加进来,还是不行,楼主可以发个完整的文件夹给我吗,release模式的,debug模式没问题

    read more
  • 1.jpg

    简介

    USD全称是“Universal Scene Description”,它主要着力的是电影、游戏复杂制作的流程的规范化。这回我们主要来研究USD在Maya中有关渲染部分究竟是如何实现的。

    USD通过附属的子项目Hydra来实现在其工具“UsdView”以及Maya中渲染方面的实现。Hydra是一款经过多年锤炼的渲染引擎(据说自从2012年就开始研发),Hydra与Maya中有关渲染的结合得益于Maya支持第三方渲染框架通过它提出的“Viewport (1.0、2.0)”方式支持。

    初探

    我们打开USD项目,会发现它有很多子项目。其中包含了imaging和usd子项目。这里我们主要关注的是imaging项目。由单词意思可知,其主要关注的是产出图片的,也就是有关渲染的。
    ca7ce8ff-7e7d-4ec3-9246-fbee48109c7b-image.png

    有关Hydra是三个子项目。包含了hdSt、hdStream以及hdx。我们主要关注的是hdSt。因为这个项目是和OpenGL渲染密切相关的。由于OpenGL是做渲染的大家通用知识,因而它是我们关注的主要子项目。

    调试

    调试.jpg
    我们调试这部分代码,截了此图。我们在图中至少发现几个问题:

    Maya底层是用OpenSceneGraph(OSG)管理场景的。我们可以看到Maya 2018的文件夹里有很多OSG开头的动态链接库,这么说具体视口渲染的部分都是建立在OSG上的。Maya的Viewport 2.0也是建立在OSG上的。在可见的将来它们不会替换掉OSG。

    USD的Hydra和Maya结合的类叫做UsdMayaGLBatchRenderer。由名字可知,它仅工作在OpenGL下,换句话说,如果Maya使用的是DirectX11进行渲染的,那么它将会失效。
    d7063ec4-242e-40bd-bf05-5103c38fedf1-image.png

    libhd项目只是一个前端库,后端通过libhdx以及libhdSt来实现。尤其是libhdSt,它主要是和OpenGL打交道的。它十分复杂。主要基于的是OpenGL 3.3+,也就是包含了各种着色器以及高级栅格化技术,并且整合了网格细分库:OpenSubdiv。

    后续

    由于我们的研究方向是思考一个方式来让让Hydra支持过程化纹理,因此我们还需要继续对此进行研究。

    read more
  • 最近我开始在Maya工作啦。Maya是一款优秀的三维软件,可以处理布景、建模、纹理、装备、渲染等操作。而且它可以支持C++和Python的开发。文档也是非常多的(参考这里)。

    我们最近的工作呢,是想要利用Maya的资源,尤其是图片资源,来制作新的界面。由于Maya是基于Qt开发的,因此要获取图片资源,除了Maya文件夹里D:\Develop\Autodesk\Maya2018\icons文件之外,还需要从资源文件中获取到。而资源文件一般是编译成C++代码放在程序的某个位置了,所以我们一般是看不到的。我们就开始想,既然Maya能够成功地读取并且显示,我们通过Maya的插件开发,不也能够获取并且显示到需要的图标吗?由于Qt的经验,我开始研究通过写Maya插件来获取到Maya资源文件的方法。

    新打开Maya软件,点击右下角的脚本图标,我们开始输入脚本:
    ad141613-63a5-4a93-9b79-3d6ca44da782-image.png

    2、我们通过Qt for Python来从Maya中获取到图标信息。由于Maya是构建在Qt 5.6.1上的,当时还不叫Qt for Python,而是PySide2。当然用法也是差不多的。更重要的是Qt 5.6.1已经支持QML了,可以支持QML的基本绘图方法。所以我们打算结合Qt for Python和QML来实现相关的功能。其实这样一组合就和Maya没有什么关系了。剩下的都是Qt的技术。
    我们的脚本是这样的:

    from PySide2.QtQuick import QQuickView from PySide2.QtCore import QDir, QFileInfo, QUrl def getMayaResourceFileList( nameFilter ): dir = QDir( ":/" ) return dir.entryList( nameFilter ) view = QQuickView( ) view.setResizeMode( QQuickView.SizeViewToRootObject ) mainUrl = QUrl.fromLocalFile( "C:/Users/huawei/Documents/ImageGridView.qml" ) view.setSource( mainUrl ) view.show( ) rootItem = view.rootObject( ) if rootItem != None: rootItem.setProperty( "prefix", "qrc:/" ) rootItem.setProperty( "model", getMayaResourceFileList( '*.png' ) )

    其中C:/Users/huawei/Documents/ImageGridView.qml是我本地的路径,可以改为任意的路径甚至是http路径呢。

    我们还得完成ImageGridView.qml文件内容,其实也非常简单,大概是这样的: import QtQuick 2.6 GridView { id: root width: 320 height: 480 cellWidth: 80 cellHeight: 80 delegate: Image { width: 80 height: 80 source: root.prefix + modelData Text { anchors { left: parent.left right: parent.right bottom: parent.bottom } text: modelData wrapMode: Text.Wrap } } model: 40 property string prefix }

    22ae8472-0bb3-4342-ae72-e1cb54bd87a7-image.png

    4、这些文件准备就位了!我们打开一下Maya软件,看看结果~
    f430332d-c824-47d3-8d1c-b17b3c53bc97-image.png
    它是一个可滑动的界面,每行显示4列,然后下面是文字的内容,展示了图标的名称。我们可以借此工具拿到我们感兴趣的图标的路径,然后应用到我们制作的界面上。其实,如果你觉得图片好,也可以通过QImage以及QPixmap给转存出去自己用。

    如果你觉得文章很棒,记得点赞哦。

    read more

关注我们

微博
QQ群











召唤伊斯特瓦尔