Taskbus - 基于Qt的跨平台多进程合作框架(零)形散神聚的架构设计

2018-11:该想法已经被实现,请参考https://blog.csdn.net/goldenhawking/article/details/84191631

1.非计算机专业学术团队的业务特点

非计算机专业学术团队是一个泛泛的概念,即没有受过专业化的软件工程化训练,又精通某个非计算机行业知识的学术团队。诸如需要频繁自行开发小工具进行算法验证的高校教研团队,以及深入某一领域(化工、机械、通信、电子等)进行数据分析,需要长期从事非消费类工具软件开发的工程师团队。他们具备这些特点:

  • 计算机专业人才引进少,且经验不足;
  • 专业能力突出,开发水平停留在毕业论文水平;
  • 对高校而言,还存在人员流动很快等问题。

行业实验室

笔者曾作为行业学术团队的一员,尝试为团队内部构造一个能被广泛接受的生产力工具链。目的是尽可能多地保留各届毕业生们实现的算法逻辑,使其能够用于后续的合作项目开发。经过了十几年的尝试,先后尝试了4套方案,均效果不佳。

2 四个失败方案

从这四个失败案例所在时代来看,本身技术先进性都不是很差。在尝试这四个案例之前,教研室的项目都是靠直接嵌入代码的方法来引用现有技术的。

2.1方案一-动态链接库

设计实现于2004年左右。当时,本硕教学、项目开发采用的均为Visual C++ ,按照导师的要求,学生们的所有算法模块需要封装为动态链接库,以便可以很快的嵌入到新的项目中使用。作为对计算机稍微熟悉一点的助教,我和几个同事一起规范了函数名称等规则,便直接试用了。当时我们老师的水平也是停留在工具、架构搅和在一起,糊里糊涂的地步。这个方案试用了2年,发现了很多问题:

  1. 要求不够细。对动态链接库封装技术的掌握各人并不相同。用过MFC的老人们应该记得,VC平台导出DLL就有好多种不同的形式,各自对依赖项的引用也分静态链接、动态链接、动态加载等方式。很多学生被要求重新封装,非常不高兴,抗拒这个规则。
  2. 错误难定位。学生们个性很强,每个人习惯千差万别,代码中BUG千奇百怪。作为教研室,很难找到专业的测试人员,最终两个项目平台运行起来,不是这里报错就是那里报错,且很难定位是谁的库有问题;开发者本人毕业后,根本没有人愿意去碰他的代码。
  3. 跨语言集成难。与某公司合作,该公司使用的是当时最为新颖的.Net技术,使用C#调用不安全的DLL逻辑,出现的内存泄漏(估计是学生在malloc之后没有free)、野指针等问题使得项目几乎失败。

2.2方案二-COM

2006年左右,当时与计算机系合作,要来了两个对COM组件技术很熟悉的博士生。那一阵子被COM、ACE的设计模式洗脑了,总觉得应该在教研室内部实现一套类似计算机系“基于COM组件”的啥啥自动处理系统云云。两位博士很尽责,很快新的框架、接口就完成了。“二进制兼容、语言无关、,这太好了!”

MFC这里写图片描述

由于实验室最熟悉的还是MFC、C#,于是乎,一场轰轰烈烈的学习运动开始了。但是,热乎劲只持续了几周,大多数人就打了退堂鼓,方案不了了之。原因:

  1. 计算机水平差。让大多数只学过“C语言编程”这门课的非计算机专业学生,猛然一下尝试这种复杂的技术,是很失败的决策。
  2. 学习曲线陡峭。学生们不愿意投入这么大的精力,去把Matlab的逻辑变成C语言的算法。毕竟毕业压力太大。
  3. 错误调试难。人员计算机专业能力差,使得调试更麻烦了。没办法跟踪COM组件内的错误,压力山大。

2.3方案三-Web Service

2008年以后,基于浏览器的Web Service架构流行起来!而且,这种架构好处很多,尤其在系统发布、版本升级方面,有先天优势。实验室尝试把较为稳健的算法通过fcgi接口向Web架构迁移,赶上时代的发展。
然而,事与愿违——我们很快发现,依靠当下的团队无法驾驭Web工具链。学生们把算法移植为后台进程,并不难。但是,作为非消费类的行业应用,无论从硬件架构还是用户群来说,和消费类的框架差异都很大。放弃的原因就一个:

工具链爆炸。举个例子,原本从一块采集芯片上读取的波形,只要C++一种语言,经过有限几步处理,就能直接显示。但是用了Web后,要有后台采集进程(C)、要有处理进程(C)、webService(ASP.net)、网页控件(JS,当时还有Flash或者ActiveX)、网页(牵扯美工)。要么和外包公司合作,要么就引进人才。

在2008年左右,软件外包的成本还是比较高的。而且,作为一个不是很有名的穷学校,这些成本都是非常高昂的。

2.4 方案四-Qt插件

有了这些前车之鉴后,教学团队的老师们已经有些皮条了。直到2012年左右,与一个外包公司合作的项目因为采用了C++ Qt 的插件技术,无论是开发过程、调试体验都很棒,实验室才决定引入Qt作为标准开发框架来规范教研室内的工具链。

在这个阶段,很多可重用的代码被直接封装,采用Qt-plugins的方式嵌入到框架中去。

C++Qt
但是,这不代表没有问题,近几年,寻求改变的呼声越来越大了。主要原因:

  1. 二进制依赖性太强。Qt的库不是二进制兼容的。虽然Qt号称跨平台,那都是需要重新编译的。Qt必须要保证1级、2级版本号相同(最近5.8后,似乎C++部分的改造很小,这个结论也过时了)、编译器相同,其插件才是兼容的。因此,很多项目的Qt版本被镶死,落后主版本4-5年。不同届的学生留下的库,版本号参差不齐,问题多多。
  2. 不利于个性发展。2014年以后,很多高中都有较深的计算机教学课程,特别是近几年,熟悉脚本语言的年轻人越来越多。一些原本很复杂的工作,年轻人用python+Numpy等工具,轻而易举就解决了。这些学生没有必要再被要求去为了迎合Qt,学习C++。实验室不是软件公司,强迫别人采用自己厌恶的语言,是非常暴力并令人讨厌的。

3 工具链考虑的因素

有了上面4个失败的教训,我们这些一把年纪的家伙聚起来,好好考虑了一下工具链应该具备的特点。所有难易都是相对只学过一门基础语言、没有任何工程化开发经验的非计算机专业本科生、具备一定计算机软件开发经验,熟悉Matlab的硕士生、博士生而言。讨论的结果是下面这个表:

尝试过的工具链+架构学习曲线错误定位调试难度开发语言绑定编译器绑定学生的抗拒程度
C++动态链接库★★★★★★★
COM/OLE★★★★★★★★★★★★★
.Net WebService★★★★★★★★★
Qt插件★★★★★★★★★★★★★★★★★★
理想方案?

可是,即使是这个表,都得不到学生们的认可。因为,难度因人而异。一些人认为C++很难,但是Web很简单,一些人反之。是时候从头思考了。十几年了,老哥们。

4 理想方案——弃形求神

我们认为,经历了如此多的失败,究其原因,就是总是想选取一种大家都满意的特定语言、特定架构——这可能根本就是不存在的。打个比方,武林中,谁会去规定每个门派必须使用什么兵器?记得扫地僧拿着扫把也很厉害啊!
扫地僧
但是,不规定使用什么兵器,不代表放任不管。兵器这个“形”是外在的,不重要。一个门派内在的应该是“神”。武当、峨眉都用宝剑,但剑法不同。一些门派都是壮汉,则可练习纯阳武功,一些门派本来就是女子,于是选择诸如九阴白骨爪之类的东东。回到实验室这样的团队,只有根据自身师生能力、学科构成来考虑,把什么“先进性”之类的念头彻底抛弃,琢磨适合自己的东西。放回到我们这里,好的理想方案,应该具备下面的特点。

进程切割、语言无关、编译器无关、架构无关

4.1 方案特点

(1)进程切割

从老师角度,每个单元绝对不能混在一个进程中。因为每个人不保证自己的代码不会崩溃。
从学生角度,应该可以像本科编写作业题一样编写程序,比如直接使用printf\scanf吞吐数据。
任何逾越四年制本科教学计算机基础课程以外的特性,都是不值得提倡的。避免使用DLL、避免使用网络套接字、避免使用COM,避免使用特定环境。

(2)语言无关、编译器无关

不规定使用的语言、编译器,不规定运行的环境。

(3)架构无关

学生不需要考虑自己的这部分东西究竟是独立运行、嵌入一个EXE桌面程序运行、嵌入Web后台运行。学生的精力应该投入到主干学科,让硕士与博士们真正搞研究 ,而不是打工。

4.2 方案:基于进程管道重定向的任务总线

本方案从进程通信的标准为入口,定义一个团队内的协作开发方案。

(1) 进程管道

只要学过一门基础语言,就会遇到输入输出。一般来说,再小的一个通用操作系统,也会提供至少1对输入输出管道用于进程间通信。以C为例子,就是标准输入输出(stdio)中定义的stdin, stdout.
这两个管道直接用于类似printf、scanf等函数,或使用 fread(…,stdin), fwrite(…,stdout)来进行通信。

(2) 任务总线

如果仅通过管道,进程间其实只具备单路双工通信能力。任务总线通过一定的数据结构,在单路双工线路中,承载多个逻辑通道。如可区分专题(subject)、目的、来源地址等。

(3) 分布式任务总线环境示意图

分布式任务总线环境

4.3 学生的工作

学生的工作,就是实现上图中绿色部分所示的各个模块、文档。这些模块采用的实现方法不限制,但只要能够实现下面三个功能,即可完成任务。

(1)撰写一个JSON功能描述文档

这个JSON文档用来描述模块具备什么功能。如,一个负责采集土壤温度的模块:

temp_sp.exe.json

{
	"mud_tp_sampler":{
		"paras":
		{
			"sprate":{
				"label":"采样间隔(秒)",
				"type":"int",
				"range":{
					"min":1,
					"max":120
				}
			},
			"mod":{
				"label":"工作模式",
				"type":"enum",
				"range":{
					"1":"定时采集",
					"2":"持续采集"
				}
			}
		},
		"in_subjects":{
			"gps":{
				"label":"GPS专题输入",
				"type":"text"
			}
		},		
		"out_subjects":{
			"data":{
				"label":"采集温度",
				"type":"bytes"
			},
			"log":{
				"label":"工况",
				"type":"text"
			}
		}
	}
}

将在平台的管理界面中体现为这样的图标:

模块

(2) 接收命令行参数

在上述模块中,所有paras 在模块进程被启动时,会通过–name=value的方式传递。
如(为版面换行):

root@localhost$ temp_sp 
        --instance=2421 
        --func=mud_tp_sampler 
        --sprate=23
        --mod=2 
        --gps=1837462 
        --data=8274611 
        --log=343649

学生只要具备基础的能力,即可获得:

int main(int argc, char * argv[])
{
	//...
	{
		if (strcmp(argv[i],"--sprate")==0)
		{
			//...
		}
	}
}

一些解释:

  1. 平台会通过–instance送进来一个唯一的ID用于区分本进程。
  2. 所有参数(paras单元)都能够被平台配置,比如采样间隔–sprate
  3. 所有接口(subjects单元)都是系统根据连接关系分配的整数。这些整数都是用于区分数据的专题,用于吞吐数据。

(3) 吞吐数据

学生直接吞吐数据。

比如,在本例里,可以不断接受GPS,并判断时间、输出

#ifdef _WIN32
#include <fcntl.h>  
#include <io.h> 
#endif
struct tbheader{
	int subject;
	int channel;
	int from;
	int to;
	int len;
};
int main(...)
{
    //进入二进制模式
#ifdef _WIN32
	setmode(fileno(stdin), O_BINARY);  
    setmode(fileno(stdout),O_BINARY);  
#endif
	/*
	已经定义一个能够承载最大长度包的缓存buffer
	命令行获得的gps专题的号码存储在n_gps里,
	命令行获得的data专题的号码存储在n_data里,
    命令行获得的进程实例(--instance)号码存储在n_ins里,
	*/
	struct	tbheader header;
	while (!finished())
	{
		fread(&header,sizeof(header),1,stdin);
		fread(buffer,1,header.len,stdin);
		if (subject==n_gps)
		{
			//...
			if (time_hit())
			{
				float * ftemp = get_card_temp_buffer();
				struct tbheader out_header;
				out_header.subject = n_data;
				out_header.channel = header.channel;
				out_header.from = n_ins;
				out_header.to = header.from;
				out_header.len = sizeof(float)*OUTLEN;
				fwrite(ftemp,sizeof(fload),OUTLEN,stdout);
				fflush(stdout);
			}
		}
		
	}
	//...
}

(4) 调试

通过freopen等函数,可以改变 stdin,stdout的指向。因此,学生只要通过文件输入输出来调试自己的EXE,便可以在接入平台后,顺利处理真实的输入输出。

5 后记

使用这种架构,充分给学生选择工具链的自由。同时,彻底消除了语言、编译器的耦合关系。对水平差的学生,可以使用单线程操作;水平高的,模块里面完全又是一个复杂的系统。一些独立的功能还可以有自己的界面,不同的工作模式。
而平台部分,整体外包给软件公司,是最好的选择。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页