标签: notes docs bcd
read online:
title: 封装示意图(忽略了界面同步部分)
主UI->主Wrapper: 所有 UI 操作\n由 Wrapper 接管
note right of 主Wrapper: 本机执行?\n是
主Wrapper->主Class: 直接调用
主Class->主Wrapper: 捕获结果
主Wrapper->主UI: 在界面上有所反映
note right of 主Wrapper: 本机执行?\n否
主Wrapper->主Socket: 把调用函数和参数\n传递到另一台电脑
主Socket->从Socket: TCP 通讯\n程序运行时\n已经建立连接
从Socket->从Wrapper: 还原函数和参数,\n调用 Wrapper
从Wrapper->从Class: 直接调用
从Class->从Wrapper: 捕捉结果
note left of 从Wrapper: 异地反映?\n是\n(序列图里忽略\n了本地反映)
从Wrapper->从Socket: 回传
从Socket->主Socket:
主Socket->主Wrapper: 解析给 Wrapper
主Wrapper->主UI: 在界面上有所反映
- 主:Master,操作的界面(高性能平台)
- 从:Slave,另一台电脑(工控机)
程序只有一个,运行在两个电脑上,一个为 Master,一个为 Slave,TCP 连接建立后。操作 Master 的 UI,执行可能在
- 本机(Master),也可能在
- 另一台电脑(Slave)。
对 UI 而言,无所谓执行发生在哪台电脑(实现细节被 Wrappers 屏蔽)。
Wrapper 主要做了两件事:
- 判断执行应该在本机还是异地,并调用本地或异地的类执行实体
- 接受 Class 或者 Socket 传来的结果,并 emit 相应的信号(二传手)
Wrapper 和 Class 有几乎相同的调用接口, 所以原来在单电脑上的程序只要简单修改即可。
对程序的要求:
- 要避免直接获取 Class 的返回值(返回值难以跨电脑),而是采用 emit 信号的方式
- Class 的信号由 Wrapper 完全接管,重新发射信号(信号名称保持不变)。
- UI 的变化,都是通过接受 Wrapper 的信号。
主程序运行后,先要选择自己是 Master(高性能平台)还是 Slave(工控机):
高性能平台要先打开,并打开 TCP 网络服务器:(点击 Serve
)
再运行程序,不过这次选择 Slave,然后将工控机连接到高性能平台:(点击 Connect
)
现在两者连接上后,界面发生变化(一些按钮可以使用,一些按钮不再能使用)。现在点击下方大按钮,可以同步两者的颜色(在红和绿直接切换)。
关闭这两个界面会弹出一个 demo 界面,展示的如何同步两者的 UI 以及运算如何分布在两台电脑上,如图:
可以看到,
- “加法”运行在高性能平台上,
- “乘法”运行在工控机上。
同时,界面是同步的。下面介绍如何将原有类改善,实现跨主机通讯、UI 同步。
先明确几个概念,
类(Class) : 实际的执行实体
包装(Wrapper) : 封装了类,提供几乎一致的接口,屏蔽了跨主机的一些操作。
界面类(UI) : 界面上,调用的虽然是 Wrappers,但和调用 Classes 的方式一致。
这里的修改指南指导了如何在 Class 和 UI 之间插入一个封装
,在不改变(几乎)原有类的基础上实现跨主机通信。
比如从 ui->pushButton->setText( class->getString() )
改起。
首先,在 QString Class::getString( args )
中 emit 一个信号:
emit wrp_getString( returnValue ) // 要提前定义(signals: void wrp_getString( QString text );)
然后参照 Class 定义一个 Wrapper,提供 void Wrapper::getString( args )
函数(几乎和原有类 Class 一模一样),
把 Class 的信号和 Wrapper 的槽连接:
connect( class, SIGNAL(wrp_getString(QString)),
wrapper, SLOT(onWrp_getString(QString)) );
Wrapper 构造的时候会传入 Class 类实体指针,
如果 Class 在本机运行,Wrapper::getString( args )
会直接调用 Class::getString( args )
,
如果 Class 在异地运行,经过判断 Wrapper::getString( args )
会把函数名 ID
和参数 args
传到另一台电脑,
由另一台电脑的对应的 Wrapper 的 Class 实体执行 Class::getString( args )
。
不管哪个电脑上执行了 Class 函数,信号都会被那个电脑的 Wrapper 捕获,然后 emit 一个同样的信号,并让另一台电脑 emit 这个信号。
即 Wrapper::getString( args )
的大致逻辑为:
// Class 函数对应的 Wrapper 函数
void Wrapper::getString( args )
{
if ( Class registered on THIS_COMPUTER )
{
// 本机执行
this->class->getString();
}
else
{
// 异地执行(把函数名和参数传到另一台电脑)
send `Class::getString' + `args' to the other computer
tell THE_OTHER_COMPUTER to run: class->getString()
}
}
// 信号所连接的槽
void onWrp_getString( QString returnValue )
{
// 本机释放信号
emit wrp_getString( returnValue );
// 让异地电脑释放信号
tell THE_OTHER_COMPUTER to: emit Wrapper::wrp_getString( returnValue )
}
现在,两台电脑上,有一台电脑执行了操作(可能是你通过 UI 交互的那一台,或者是另一台), 释放了两个同样的信号,只要把这个信号绑定到各自的 UI,界面就同步了:
connect( wrapper, SIGNAL(wrp_getString(QString)),
ui, SLOT(onWrp_getString(QString)) );
最后 UI 得到这个信号,在界面上有所反映:
void Ui::onWrp_getString( QString returnValue )
{
ui->pushButton->setText( returnValue );
}
现在,
需把原来的 ui->pushButton->setText( class->getString() )
换成 wrapper->getString()
。
总结一下,原来的一句之间的 setText
,变成了间接的:
- 某一台电脑界面的操作,导致某一台电脑执行
Class
函数 Class
emit 信号给Wrapper
Wrapper
在两台电脑 emit 信号给UI
- 两个
UI
得到信号,同步更新界面
注意:
直接调用 private 成员也不可以了。
所有的 Wrappers 和 UI 都在一个全局的 Bundle 中绑定。(源码位于 Src/Wrappers/Wrappers.h
)
/*
* 获得实例
*/
Bundle::getInstance(); // 获得唯一的、静态的一个实例,包含了所有的大的类实例,UIs,Wrappers(Classes)
/*
* 本机是高性能平台(Master)还是工控机(Slave)?
*/
// who am I?
Bundle::whoAmI(); // 我是谁?MASTER 还是 SLAVE?
// 可以进行判断
if ( Bundle::whoAmI() == MASTER ) {
qDebug() << "我是高性能平台(Master)";
}
/*
* Bundle 的东西有
*/
// 主要模块的“封装”
Bundle::getInstance()->lms // LMS Wrapper
Bundle::getInstance()->lmsAgent // Wrapper 用到的界面
Bundle::getInstance()->server // 网络通信接口
Bundle::getInstance()->client // 网络通信接口
...
// 也可以获得主要模块的“类实体”(不推荐直接调用)
Bundle::getInstance()->lms->kernel // LMSReader 实体,可能为 NULL(如果不在本机注册)
...
/*
* Bundle 的一些函数
*/
// 向另一台电脑发数据
Bundle::getInstance()->send( const QByteArray &msg ); // 发送数据到另一台电脑
// 数据需要满足一定的格式,可通过一个 Moderator(“翻译”)静态类/函数进行转化,如:
// 要在另一台电脑上运行 LMSReader::genNewPath( "D://tmp/" ); (LMS 注册在那台电脑上)
Bundle::getInstance()->send( Moderator::lms_genNewPath( "D://tmp/" ) );
-
现在 Log 会在两边记录自己的部分,应该改为:退出时,1)如果 Master 退出,那么发出请求让 Slave 把 log 传过来,再重新排序,再退出;2)如果 Slave 退出,那么请求 Master 退出,然后等待 Master 的 log 请求,其余同 1)。有点 Bug 但功能已经有了。 - 因为 Log 的时间为两台电脑的时间戳,所以需要校准。连接初期,发出 M->S->M->S->M->S 的请求,得到时间戳的改正,修正 Slave 端的时间戳。(如果电脑都联网的话,就没必要弄这个)
- 把其他的界面和类改完(这个比较机械,但量多,
最近两天的工作
)
- ~~现在信号有环存在……所以有点混乱。应该不难解决。~~是界面上 TextChanged 信号老被触发的问题。已解决。
很简单,修改 Moderator 的一个 HashMap 即可:
// Master: 高性能平台
// Slave : 工控机
wss.insert( BCD::TYPE_LMS, SLAVE );
wss.insert( BCD::TYPE_MCU, SLAVE );
wss.insert( BCD::TYPE_UR, SLAVE );
wss.insert( BCD::TYPE_ARM, SLAVE );
wss.insert( BCD::TYPE_SP20000C, MASTER );
wss.insert( BCD::TYPE_MULTIPLICATION_ON_SLAVE, SLAVE );
wss.insert( BCD::TYPE_ADDITION_ON_MASTER, MASTER );
ditched.