简介跨电脑 IPC,进行函数调用;
(这部分很内容也很多。暂时没时间写完。)
电脑/进程之间的通信,可以使用 TCP,可以说 TCP 是最好的 IPC(没有之一),因为它:
唯一的不好在于它不能使用共享内存,也就是说不能使用同一个变量。可能存在变量的更新问题。But,anyway,用 TCP 来通信总是不错的。
(总有些人,要 UDP 来实现一个捉急的 TCP,效率低、效果差,早该一劳永逸直接上 TCP。)
其实以前的一篇 post 已经提到过:封装说明,可以先看这边。这个 post 讲得是架构,也就是说【如何屏蔽程序调用?不管它来自本机,还是异地】。对应的代码在 district10/CrossOS: Cross OS Communication,已经在 Linux 和 Windows 上测试通过。
在上面 repo 的 README.txt 里,写到:
还需要定义一个协议,规定相互传递数据的格式。如:第一个字节代表传输类型,文本?buf?需要保存?数据开始?数据结束?等等。
假设这个规则为 moderator,moderator 需要在发送前对数据进行封装,在收到数据时进行解析和分发。就好象这个 moderator 是我们的 PDU 解析工具。
基于这个 Wrapper,我设计了 Moderator(中介)模块,专门负责序列化和反序列化后函数调用。
它的作用如下图:
因为我曾上过的一门自己喜欢的选修课:英语演讲与辩论,里每次讨论小组里都要有一个 moderator 作为协调者。他来统领讨论的进行。
我觉得这个概念和这里完全契合。所以把这个模块叫 moderator。
Moderator 主要利用了 Qt 提供的序列化功能,实现了函数调用(以及调用参数)的的序列化。
首先要明确,我们的代码只有一份,只不过高性能计算机上会运行 Master 程序,工控机上会运行 Slave 程序,两者都有各个传感器模块,只不过传感器只有一边是真的有,另一边只是有一个空壳,它唯一的作用是请求另一边的传感器,并假装自己这边真的有传感器。 Moderator 在两边都有,下面是代码:
class Moderator
{
public:
Moderator( );
static bool doItYourself( BCD::TypeID tid );
public:
static void dispatch( QByteArray &msg );
typedef QHash<BCD::TypeID, MasterSlaveType> WhichSide;
const static WhichSide wss;
enum CrossType {
// -- G E N E S I S - L M S --
GEN_LMS__START__QINT64,
GEN_LMS__STOP__VOID,
...
// -- G E N E S I S - U R --
GEN_UNIT__SET_UR_POSE__DOUBLE_DOUBLE_DOUBLE_DOUBLE_DOUBLE_DOUBLE
};
// -- G E N E S I S --
static QByteArray genLMS_start( const qint64 &dt = 0 );
static QByteArray genLMS_stop( );
static QByteArray genUnit_setUrPose( double x, double y, double z,
double rx, double ry, double rz );
...
private:
static WhichSide initWhichSides( );
};
需要留意的地方有:
static bool doItYourself( BCD::TypeID tid )
这个函数在一侧返回了 true,在另一侧就要返回 false,因为一个函数要把在 master 上执行,要不再工控机上执行,不能同时为 true(或者 false)。它的实现代码也颇为简单:return Bundle::whoAmI() == wss.value( tid );
,其中 wss 存储了注册信息,也就是一个模块到底是要在工控机,还是高性能上运行。doItYourself 的参数是 BCD::TypeId tid,就是一个模块的编号。这个在日志模块那一部分讲过:
namespace BCD {
// types
enum TypeID {
TYPE_ARM, // 对应 BCD::ARM::...
TYPE_IMU, // 对应 BCD::IUM::...
TYPE_LMS, // ...
TYPE_MCU,
TYPE_UR,
...
};
...
}
一个传感器要注册到 Master 或者 Slave,在 moderator.cpp 里有实现:
const Moderator::WhichSide Moderator::wss = Moderator::initWhichSides( );
Moderator::WhichSide Moderator::initWhichSides( )
{
WhichSide wss;
// Master: 高性能平台
// Slave : 工控机
wss.insert( BCD::TYPE_SERVER, SLAVE );
wss.insert( BCD::TYPE_CLIENT, MASTER );
wss.insert( BCD::TYPE_LMS, MASTER );
wss.insert( BCD::TYPE_MCU, MASTER );
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 );
return wss;
}
函数的序列化,需要将函数名注册,并和本地函数绑定,enum CrossType { ... }
做的就是函数名注册的工作,它里面的每一个标志都对应一个函数,比如 GEN_LMS__START__QINT64
就对应 static QByteArray genLMS_start( const qint64 &dt = 0 )
,GEN_LMS__STOP__VOID
就对应 static QByteArray genLMS_stop( )
。
这三个函数的实现放在这里,以做参考:
为了减少输入量,TX_OUT 是一个宏(在通信领域,TX 通常指的是 transmission)
#define TX_OUT \
QByteArray tx; \
QDataStream out( &tx, QIODevice::WriteOnly ); \
out.setVersion( QDataStream::Qt_4_8 ); \
out
QByteArray Moderator::genLMS_start( const qint64 &dt /*= 0 */ )
{
TX_OUT << (int)GEN_LMS__START__QINT64 << dt;
return tx;
}
QByteArray Moderator::genLMS_stop( )
{
TX_OUT << (int)GEN_LMS__STOP__VOID;
return tx;
}
QByteArray Moderator::genUnit_setUrPose( double x, double y, double z, double rx, double ry, double rz )
{
TX_OUT << (int) GEN_UNIT__SET_UR_POSE__DOUBLE_DOUBLE_DOUBLE_DOUBLE_DOUBLE_DOUBLE
<< x << y << z << rx << ry << rz;
return tx;
}
其实就是把函数的标志号和参数一一序列化。
dispatch 函数,就是收到反序列化为一个操作,然后如何调用具体的模块:
void Moderator::dispatch( QByteArray &msg )
{
int flag( -1 );
QDataStream in( &msg, QIODevice::ReadOnly );
in.setVersion ( QDataStream::Qt_4_8 );
while ( !in.atEnd() ) {
in >> flag;
if ( false ) {
}
...
else if ( GEN_LMS__START__QINT64 == flag )
{
qint64 ts;
in >> ts;
if ( Bundle::activated() && Bundle::whoAmI() == SLAVE ) {
Bundle::getInstance()->m_LMSReader->start( ts );
}
}
else if ( GEN_LMS__STOP__VOID == flag )
{
if ( Bundle::activated() && Bundle::whoAmI() == SLAVE ) {
Bundle::getInstance()->m_LMSReader->stop();
}
}
...
else if ( GEN_UNIT__SET_UR_POSE__DOUBLE_DOUBLE_DOUBLE_DOUBLE_DOUBLE_DOUBLE == flag )
{
double x, y, z, rx, ry, rz;
in >> x >> y >> z >> rx >> ry >> rz;
if ( Bundle::activated() && Bundle::whoAmI() == SLAVE ) {
// config ur pose
if ( Bundle::getInstance() && Bundle::getInstance()->collectionUnit ) {
Bundle::getInstance()->setURPose( x, y, z, rx, ry, rz );
}
}
}
else
{
}
}
}
至此,问题都解决了。