//评注:这是一位朋友的关于语音卡开发的开源框架,历时约一年。初步在.net实现,并有实用案例。后期希望开源并借助大家的力量完成c++,delphi的版本。其目的是使开发爱好者从某个台阶起步,更快的进入到语音开发领域。

//文章可转载,但请勿添加任何其他链接造成错误指向。保持文字完整和文字的链接完整。

//解释权归原作者,如果您想参与开源框架的完善,或新增其他语言版本,请与作者协商。

==============================================================

语音卡开发系列: LightweightCTI架构设计

文档名称:LightweightCTI架构设计

作  者:东成西就

版  本:V1.0.0.0

适用架构:LightweightCTI for Delphi/C++Builder 1.0.0.0

修订次数:

创建时间:2006-08-26

修改时间:

联系作者: Blog: http://www.lightweightcti.cn/blogs/jackyxu/default.aspx

Mail: Sjteksoft@gmail.com

爱好者群组: QQ群: 4624353

直接链接: www.lightweightcti.cn  www.lightweightcti.cn:8080/svn/lightweightcti/trunk

板卡&开发平台供应商的链接:

深圳东进:http://www.donjin.com/donjinbbs/Default.asp

语音卡开发语言&平台供应商: 深圳市蓝星际: www.bluespace.com.cn

三汇: http://www.sanhuid.com/

目     录

一、序言... 2

1.1、LigthweightCTI定义... 3

1.2、本文的目的... 4

1.3、本文适合谁... 4

1.4、可参考资源... 4

1.5、感谢... 5

二、LightweightCTI体系结构... 5

2.1、系统组件容器... 5

2.2、系统扩展组件... 5

2.3、全局属性、公共服务... 6

三、板卡适配器层... 7

3.1、通道管理器(ChannelManager)... 8

3.2、板卡适配器(CTICardDriver)... 10

3.3、通道(Channel)... 11

3.3.1、通道接口定义... 12

3.3.2、两种开发模式对比... 13

3.3.3、通道工作机制... 14

3.3.4、脚本引擎的挂接... 16

3.3.5、采用TTS放音... 17

四、适配器层定义的服务... 19

4.1、任务管理器(TaskManager)... 19

4.1.1、任务队列填充器(TaskQueueFillter)... 20

4.1.2、任务(Task)... 21

4.1.3、会话(Session)... 21

4.2、临界区服务(CriticalSection)... 21

4.3、日志服务(Log Service)... 23

4.4、XML解析服务(XMLParse Service)... 24

五、适配器层整体结构图... 25

六、应用LightweightCTI的软件结构... 26

6.1、框架整体设计模式... 26

6.2、应用LightweightCTI的软件结构... 26

七、LightweightCTI发展规划...27

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一、序言

八月初在完成公司催缴与查询系统后,有时间坐来整理一下自己在LightweightCTI方面思路。经过反复的思考,我决定抛弃写应用框架的想法,为什么呢?其实也十分简单,因目前工作性质方面的原因,没有机会去收集和验证很多不同行业的应用需求,而在缺乏有效需求的情况下是没有可能写出比较完善的应用软件,所以就没有必要去浪费时间写一套不适用也没有人用的应用软件:)那LightweightCTI就此停止了吗?当然不,其实使用它我们还是可以做许多事情的,特别的其本身也还有许多工作未完成。于是有了第三次对LightweightCTI的重构工作(这个在后面会详细的讨论),也才有了这篇短文。首先让我们来看看本人写的第一个催缴程序是如何的吧。

(图1.1 月份完成的第一个语音程序)

一般的开发人员在写此类软件也大概如此,有经验的朋友一眼就看出来啦,这是根据东进的例子改过来的程序。大家是不是觉得十分笨拙呢?让各位见笑啦,难道就没有更好、更方便的方法了吗?让我们再看看如何运用重构后的LightweightCTI框架来写语音应用业务逻辑的。

(图1.2 模拟CallAnalyze演示程序)

这其中的差距是不是很大呢?这就是LightweightCTI的作用,它能够帮助开发人员以最少的代码完成尽可能多的工作,开发人员通过LightweightCTI将不用再关心底层的语音卡是如何工作的,当然你也可以为LightweightCTI添加其它功能与接口,使之更加完善以适应你所在的企业环境。

1.1、LigthweightCTI定义

简言之LightweightCTI是一套旨在帮助开发人员迅速构建语音相关应用的一套开放源代码的基础框架,目前你下载和使用的是构建于Pwin2000 + Delphi/C++Builder环境下的框架,所有的基础性代码使用Pascal语言完成。LightweightCTI的最终目标不是成为一套可在多种语音卡基础上使用的IVR软件(尽管目前其仅仅支持东进模拟卡的大部分功能),而是希望其成为语音应用开发的基础性框架,为此它当然需要有跨开发语言、跨语音板卡的能力。当然在写本文是我还只是做了一点点的基础性工作而矣,也希望通过它来促成LightweightCTI本身的发展,使其真正成为大家在开发语音应用时可用、能用的框架。通过本次重构其本身的架构已经基本定了下来,而且本人也居于它开发完成了公司的语音催缴与查询系统,所以其完全可应用于一般的应用开发中。同时,为了便于大家对LightweightCTI进行扩展,在接下来的时间我会为其写一套可插拨的插件容器,只要符合其接口规定的组件都可以成为LightweightCTI的一部分。另外一方面,希望通过此次发布收集大家的意见以便进一步完善其设计。

使用LightweightCTI可带来哪些好处呢?

Ø         简化语音应用的开发难度,使开发人员可以将精力集中于企业业务逻辑的开发;

Ø         屏蔽不同厂商、不同型号语音板卡之间的差异,使你的开发成果能够在不同环境之下很好的复用;

Ø         有利于进行团队开发,不同的业务逻辑或基础组件都可以并行的进行;

Ø         ……

以上是其能够开发带来的一些主要优点,当然,从其它层面给企业带来的方便地球人都知道,因本文的重点不在于此,所以也就不再罗嗦啦。

1.2、本文的目的

这也算是一种设计吧,事后的设计:)当然我们的LightweightCTI还只是一个开始,并不是说所有的工作都已经完成了,应该说是在某个已经可以工作的原型基础上,停下来审视已经完成的工作,并对其中不完善的地方进行持续的重构、迭代,以使后期的工作可以轻松一些,俗语说得好磨刀不误砍柴功嘛。本文中包含了LightweightCTI框架的所有详细设计内容,对于其工作原理、实现方式、如何使用都进行了讨论,是LightweightCTI开发和使用人员的必读文本。

1.3、本文适合谁

既然谈设计就是要让使用LigthweightCTI的你明白它是如何工作的,所以本文面向的主要读者应该是决定使用LightweightCTI进行语音开发的程序员,通过本文将使开发人员明白LightweightCTI的工作机制及其中应用的一些软件设计模式。同时,通过LightweightCTI也想让大家知道如何通过面向对象技术来构造应用软件。其次,本文也适用于系统分析师、软件设计人员作为面向对象软件架构的一种参考资源,而不论你打算将其作为正面或反面的教材给你的组员介绍;&再次,对于那些正在或准备使用Delphi进行应用软件开发的程序员来说,蕴含于LightweightCTI项目内的一些技巧将使你受益非浅;最后,希望通过对其详细的剖析让刚进入软件设计领域的朋友知道,在面对一项陌生任务时如何着手解决你所面临的问题,也省去了老是回答一些只要使用GOOGLE等搜索引擎简单查询一下就可以知道答案的初级问题。

1.4、可参考资源

1、  深圳东进公司的网站http://www.dj.com.cn;

2、  一个专业的CTI网站http://www.ctiforum.com/;

3、  Delphi 5开发人员指南;

4、  Delphi源代码分析;

5、  敏捷软件开发 - 原则、模式与实践;

6、  重构 - 改善既有代码的设计;

7、  伴随LightweightCTI成长的QQ技术群4624353,是群里的兄弟在我遇到困难时无私的帮助我;

8、  LightweightCTI for Delphi/C++Builder 1.0.0.0的源码包。

1.5、感谢

要感谢我的夫人冷碧莲,在LightweightCTI的开发过程中尽管她已有身孕,但仍操持着几乎全部的家务,让我能够安心的完成软件开发工作,还有我们共同期盼着的宝宝,虽然很久很久以后你才会知道,是你的降临让我变得成熟与坚毅起来。同时,还要感谢我的同事及好友王东清先生为我创造了宽松的开发环境,在我遇到困难时向我伸出温暖的手。最后要郑重地感谢bluesen兄对于我在语音方面的开发给予了很大的帮助和支持,他的Koodoo平台是语音应用方面比较成熟的平台,感兴趣的朋友可以访问他的网站http://www.bluespace.com.cn。在此,我只想说的一句话是 谢谢你们!

二、LightweightCTI体系结构

在深入介绍LightweightCTI之前让我们首先大概了解一下其体系结构,这样有助于我们理解其工作原理、实现方式,特别是其中应用的一些设计模式。LightweightCTI的首要设计目标是为各种语音卡接口的实现提供一套支持框架,而非前面提到的为语音应用开发提供可用的实现框架(当然我们也会实现一些特定厂商平台的相关接口)。所以,我们将重点讨论包含于LightweightCTI中的基础接口,并阐述它们是如何相互协作一起完成支持语音相关应用开发任务的。

2.1、系统组件容器

它是一个可扩展的基础结构,通过它可以将各种不同的功能扩展组件有机的粘合在一起,并使这看起来像更像是一个有机的整体,所以说它为LightweightCTI的粘合剂一点也不为过。系统组件容器具体的表现为,系统插件管理器及其对应的插件接口,由它们共同组成系统的核心层,并负责扩展组件、公共服务、全局属性的注册与管理工作。

2.2、系统扩展组件

扩展组件完成具体的功能,并为其上层组件提供服务。通过将不同的扩展组件注册到容器中即可组装成不同的应用系统,同时利用它也可以对应用系统进行分割,为系统的团队开发、调试、版本控制及部署提供方便。

2.3、全局属性、公共服务

为完成某个任务系统中的不同组件可能都需要一组公共的服务及全局属性,而它们则是专门负责这方面工作的。在系统的基础结构中提供了一组预定义的公共服务如文件读取、配置信息的读写等,当然你也可以针对自己的应用编写并注册相关的服务。

为此,LightweightCTI是建立在以上三种类型的组件所构成的微核心系统之上,具体的功能则由插入到内核上的一个或多个扩展组件构成。

系统的体系结构如下图所示:

(图2.1 LightweightCTI体系结构图)

为保证系统的扩展性,采用插件树的形式来组织注册到组件容器(插件管理器)中的扩展组件。插件树是一种树形结构,在系统中管理和维护所有的插件,并对每个插件提供的功能进行描述。插件树通过插件定义文件(.addin)的一种XML文件加载所有的插件,在此我们不想就为什么采用XML文件来定义插件而展开讨论,关于它的优点已有无数的文章进行了详尽的阐述。而插件则将采用DLL或其它形式(如Delph/C++Builder中的Package)进行封装。

加载插件后的系统内部组织结构如下图所示:

Addin4

AddIn Tree

Addin1

Addin3

Addin2

Addin5

(图2.2 系统内部组织结构)

由插件树构成的系统有点类似于操作系统中的目录树。插件树中有两种不同的对象:节点与路径。插件树中的节点是包含某种特定功能的模块,由根节点加上路径构成。应用系统的功能就由插入到系统中的节点来定义,而路径则是由根节点到子节点的边线构成。如AddInRoot/SubPath1/SubNode1/SubPath2,这非常像操作系统中的文件/目录路径,节点则是包含行为的特殊路径。通过此种方式来划分软件结构的优点在此处就不多说啦。由于时间及精力的问题本部分的实现部分正在进行中。下面我们将重点介绍其中一个扩展子系统-板卡适配器层方面的内容,这也是大家最为关心的部分。

三、板卡适配器层

为了能够驱动语音卡进行工作,需要如下组件相互协作来共同完成。ChannelManager是工作站层面的虚拟机,由它加载板卡适配器并使系统进行就绪状态;CTICardDriver板卡适配器,通过其命名我们就可以了解到其主要是对板卡驱动程序的封装,完成驱动的加载与卸载工作,适配器的另外一个重要任务就是在ChannelManager的初始化进程中创建其支持的通道;Channel语音通道,实现了内线通道、外线通道、录音通道、传真通道及虚拟通道等具体类,在创建时由具体的适配器进行实例化工作。其类结构层次如下图所示:

(图3.1 板卡适配器层的类结构)

此图是在进行本次系统重构之前完成的,虽然已经初具LightweightCTI的雏形了,但是其中仍然存在一些问题:

1、  每个类需要完成太多的工作,以至于有些类显得很“肥”,这违反了单一职责原则(SRP),从图中我们可以看到AbstractChannel中包含了Debug、RunScripts及Run等本应该属于脚本引擎接口的一些方法;

2、  类之间的依赖过多,违反了面向对象设计的基本原则 – 封装性,信息隐蔽也就没有办法很好的实现啦:)

……为此上图并不是当前LightweightCTI源代码所支持的结构,当然至少是优于上图的。我们将结合源代码详细对每个接口进行介绍。

3.1、通道管理器(ChannelManager)

由上图我们可以看到通道管理器充当着十分重要的角色,它不但需要负责维护适配器的加载与卸载工作,而且还兼管着系统中所有已经初始化的通道。有点类似于MVC模式中的控制器的角色。ChannelManager的详细接口定义如下:

(图3.2 ChannelManager接口定义)

在代码中我们看到了CTICardDrivers及Channels两个属性的定义,通过它们就可以检索到系统中已经初始化的板卡适配器及所有通道。而将CTICardDrivers的访问级别定义为保护级是并不想将其公开给应用开发人员使用,他们也不关心是如何实现适配器的。而应用开发人员需要使用Channel实现具体的业务功能,所以将其访问级别定义为公共级。那么通道管理器是如何来驱动适配器及通道的呢?让我们看看下图就明白啦。

(图3.3 适配器层初始化的时序图)

以下为通道管理器初始化时的代码片段。

(图3.4 通道管理器初始化代码片段)

从1721行到1753是从系统配置文件中读取每个适配器的配置信息并根据配置的类型实例化相应的适配器(对应于时序图1.2CreateCTICard消息),值得注意的是系统目前已经支持从DLL或系统注册的类中加载适配器。这样将使系统更具灵活性,如此在部署系统以后将可以根据配置信息来改变系统所使用的语音板卡适配器,而不用重新编译系统。在实例化适配器时并没有立即对其进行初始化,而是先对其进一步配置,然后在第1762行发出Initialize消息(对应于时序图1.2.1Initialize),在适配器的初始化例程中创建具体的通道对象。

3.2、板卡适配器(CTICardDriver)

板卡适配器的功能之一是完成对应语音卡驱动程序的加载与卸载工作,除此之外它更重要是充当类工厂的角色,适配器要完成通道对象(Channel)的创建工作。在图6中还不十分明显,请看下图。

(图3.5 板卡适配器与通道的类结构图)

通道管理器并不需要知道如何创建每个通道,这个工作交给适配器来完成。为此将可以很好的屏蔽通道实例化的细节,同时,也可以根据系统配置信息灵活改变所要创建的通道类型。在上图中通过简单的应用工厂方法这一设计模式就大大的提高了系统的灵活性,使得对于适配器及通道的创建工作都不用硬编码到程序中。

适配器的接口十分简单,其中只包含了2个只读属性与4个方法:

(图3.6 板卡适配器接口)

另外,为了能够在适配器的加载与卸载过程中通知到用户程序,在适配器基类中加入了三个事件,用户程序将可以通过事件回调函数来控制适配器的初始化、释放允许、释放工作。

(图3.7 板卡适配器通知事件)

3.3、通道(Channel)

通道是支持语音相关业务应用开发的组件,只有通过它才能完成如挂机、摘机、来电显示、播放语音等具体功能。在LightweightCTI中它与应用逻辑最靠近,但是它又是最为简单的一个类。

3.3.1、通道接口定义

(图3.8 通道接口)

在Channel接口中除了为数不多的几个属性外,其余都是一些与电话语音相关的函数,在具体的实现类中将由它们完成拨打电话、播放语音等操作。

接口函数主要分为以下几类:

Ø         振铃以及摘机、挂机函数;

Ø         获取主叫和被叫号码函数;

Ø         拨号及放音函数,包括通过文件放音或TTS放音函数;

Ø         录音函数;

Ø         连通函数,通过此类函数可以实现内线外呼、外线转人工及电话会议的功能。

看到这里,你不禁会问就这点接口怎么实现实际应用中的催缴、语音查询、固话彩信等功能啊?各位看官莫急切听我慢慢道来。说到这里让我们回过头来再看看图1所帖出的代码,里面有挂机检测、对方忙检测、无人接听以及所有这些情况下对应的业务逻辑。虽然其让人感觉好像是一个完整的语音应用啦,而这也正是程序本身的蔽病所在。

3.3.2、两种开发模式对比

业务逻辑与板卡本身的处理混合在一起,程序结构一点也不清晰,如果想知道一个业务是如何完成的,必须在各状态节点之间跳来跳去,阅读这种程序是否有点头晕呢?如此程序扩展或后期的维护工作都很困难。如想增加一个处理用户按错键的状态,也必须清楚的理解所有程序逻辑才能进行改动,否则的话将使程序陷入混乱之中,而通过对Channel的封装你将可以像编写一般的程序一样,轻松的完成业务逻辑,开发人员可以将更多精力集中于系统业务的开发。下面是使用LightweightCTI模拟电话银行的程序片段:

(图3.9 东进电话银行模拟程序)

实现电话银行演示程序总共的代码仅仅42行,而采用原始状态机方式完成电话银行演示系统的代码,大家打开演示程序就明白了。当然,在这简单明了的演示业务逻辑背后LightweightCTI框架做了许多工作,比如上面说的挂机检测、摘机、放音及获取主叫或被叫号码等函数。那么Channel内部究尽做了些什么工作呢?让我们抽丝剥茧一层层揭开它神秘的面纱吧。

首先,从板卡驱动层面来看,Channel只是板卡的一种资源(外线、内线、录音及传真等),因此驱动接口(API)都将其作为一个参数来看待,如东进公司DBDK开发包中的相关函数RingDetect(wChnlNo: word): Boolean,这样系统中多条通道要同时运行的话就需要对每个通道进行轮询(也就是一个循环),当业务逻辑复杂后整个循环体少则数百行代码多则数千行,这是相当恐怖的;

其次,板卡驱动层的接口代码与业务逻辑代码混在一起加重了程序的复杂性,因而业务逻辑的修改变得十分困难,更降低了软件的复用性,如果从一种板卡移植到另一种板卡非要重新写过全部程序不可;再次,系统中满眼都中各种状态,其要么进行了一下定义如Welcome1、Hint、GetIVR等,要么干脆是一些100、200、300的数字,而程序逻辑则穿梭于这些状态之间,就如一碗意大利面条,这样的程序又有几个人看得懂呢?

最后,也是最重要的一点是程序在一个循环内不断的轮询来自每一个通道的信号,致使所有系统资源都被你的语音应用程序占据着,经常有人问怎么自己的程序都没怎么运行而CPU占用率却在70/80%以上,这就是问题所在。

而采用LightweightCTI框架中的Channel进行语音应用开发,上述提到的种种问题都将迎刃而解。为此,我们首先要解决的是让每个通道在自己独立的工作空间运行,在Windows环境下有进程、线程两种方式。经过考察进程显然不能满足我们的要求,一方面对进程进行初始化需要耗费很多资源与时间,另一方面,进程之间的通讯也比纯种麻烦,如利用多通道搭建电话会议等。那么剩下的线程方式是否符合我们的要求呢?答案是肯定的。在此我不想费口舌来解释线程的种种优点。

3.3.3、通道工作机制

通过下图可以帮助我们更加清楚的了解Channel的接口及其所依赖的接口:

(图3.10 Channel及其依赖的类接口)

从图中我们可以清楚的看到与Channel直接相关的只有脚本引擎(IScriptEngine),通过脚本引擎即可将语音应用相关的业务逻辑与板卡驱动接口隔离开来,做到互不影响即有利于程序的移植,也可以在系统运行时(不停机的状态下)动态的插拨应用组件。那么系统又是如何实现的呢?

(图3.11 执行通道业务的业务逻辑代码)

在启动通道线程执行用户程序时依次将执行下列操作:(1)、检查通道是否配置了可用的脚本引擎,如果存在的话则直接调用引擎执行相应的脚本并将执行中遇到到错误或需要记录的资料通过AOutPuts参数传回来,传回的AOutPuts参数通过脚本完成事件使得用户程序有机会进行处理;(2)、如果没有配置有效的脚本引擎的话,系统将检查是否为其编写了业务逻辑处理事件(FOnRun)若存在的话则直接调用。以上两个都存在时系统将会抛出异常阻止线程的执行。通过以上两种途径就实现了业务逻辑的分离,是不是十分简单呢?

你更适合采用哪种方式来实现业务逻辑:

Ø         脚本执行引擎 通过脚本引擎可以将业务逻辑存放在单独的文件中,并可以随时根据业务的变化进行调整而不需要重新编译整个应用软件。部署起来也十分方便,你只需要将脚本放置在语音软件可以访问到的地方即可,比如放到应用服务器上语音应用则部署到一台工控机上;

Ø         业务处理事件 适合于较小型的语音应用开发,在业务逻辑变化不大时应用业务逻辑处理事件将会十分方便,也不需要单独部署脚本项目;在LightweightCTI的Demo中我也会演示如何将其分割到独立的文件中,以便你在不重新编译系统就可以更改企业业务流程;

3.3.4、脚本引擎的挂接

你可以根据自己的实际情况来选择采用哪种方式。下面将通过一个实现IScriptEngine接口的例子详细介绍如何使用脚本来执行你的业务逻辑。首先,需要申明的一点是下面的这个例子并不是真正的脚本引擎,但同样也完成了我们需要做的工作,由此也可以看见LightweightCTI架构是多么的灵活。

TAbstractDemoScriptEngine类只简单的实现了IScriptEngine接口(图13)中的三个方法,并添加了必要的属性,详细的类定义见下图:

(图3.12 演示项目脚本引擎定义)

由于它并不是真正的脚本引擎,所以其对于LoadScripts及Debug方法只是进行了申明而没有实现代码。而隐藏了真正实现细节的RunScripts方法内部都做了些什么工作呢?让我们看看下面的代码片段。

(图3.13 RunScripts实现细节)

首先,将检查是否已经正确的初始化了通道对象,没有通道对象怎么进行工作呢?所以它是不可少的,否则的话系统抛出异常阻止线程调用脚本引擎继续执行;其次,系统写下日志记下脚本开始执行的时刻,这样以利于在程序调试时进行跟踪让开发人员或系统管理员了解脚本都做了些什么工作,在系统日志部分我们将会详细的说明它的好处;最后,执行用户在Run方法内定义的业务逻辑,可以看到在RunScripts方法内最关键的就是这一句,其它都是为其做准备。值得注意的是,我们是将Run方法包含于try…except块内的,这样能够在LightweightCTI捕捉业务逻辑中的异常。在具体的应用程序中用户只需继承TAbstractDemoScriptEngine并实现Run方法就可以了。

(图3.14 电话银行演示程序定义)

那是相当的简单,短短的4行代码就完成了其定义。接下来让我们看看实现业务逻辑的Run方法都做了哪些工作。下图所示的外呼演示实际上也只使用了几行代码。

(图3.15 外呼演示的Run方法)

3.3.5、采用TTS放音

在通道中不但可以使用PlayFile方法进行文件放音,还能够容易的挂接第三方TTS引擎实现TTS放音。在详细介绍之前请先回到图13,在图中通道并没有直接与ITTSEngine发生关系,而是通过ChannelManager来调用的。早在对LightweightCTI进行本次重构之前是将TTS引擎放在板卡适配器(CTICardDriver)类中定义的,这样使得Channel不但依赖于通道管理器也依赖于板卡适配器;另一方面,在重构后LightweightCTI具备支持多厂商多板卡的能力,而在实际的应用环境中每台语音虚拟机只需要用到一种TTS产品,所以将TTS引擎提到通道管理器中。在实际的应用中ITTSEngine也并不做实际的工作,它仅仅是具体TTS产品的一个代理或者说是Channel放音的适配器而矣。

TTS引擎的接口也十分简单,如下图所示:

(图3.16 TTS引擎接口)

TTS引擎中我们只定义了两个方法PlayMessage与PlayToFile,分别对应于文本放音和将文本转换为语音文件,通过它们将可以满足大部分的语音应用开发。下面让我们一窥通道是如何借助TTS引擎来放音的。

(图3.17 通道使用TTS放音)

通道首先检查TTS引擎是否正确的初始化,如果没有进行初始化或系统中没有配置TTS引擎系统将抛出一个异常,否则使用TTS引擎接口进行放音,实际的放音还是通过相应的TTS产品实现的,如微软、IBM等TTS产品。下面是通道借助TTS引擎(适配器)进行放音的时序图:

(图3.18 TTS放音时序图)

OK,关于通道的基础性功能介绍就到这里,这时你禁会问“哦,就这么点我怎样实现哪怕是最简单的催费任务呢?”下面我将对其进行详细的介绍,首先想阐明的一点是对于绑定服务功能最多的通道管理器(ChannelManger)我并不是十分满意,为什么呢?设计有点僵化,后续的工作中将按照前文所提出的体系结构重点对其进行重构和迭代。

暂且抛开它不表。

四、适配器层定义的服务

对于上文提到的催费任务的问题,我们一方面可以通过脚本引擎来完成,在脚本中固化任务的代码,如从系统外部生成一个待催缴的客户资料信息,然后检查电话号码是否可用,如果可用则采用此号码进行催缴,待完成催缴后再通过脚本将结果保存起来;另一方面,使用适配器层提供的服务来完成这个工作,而脚本只负责按照既定的业务流程进行催缴,不再从事生成任务、保存结果等工作为,下面将重点介绍第二种情况的实现。

4.1、任务管理器(TaskManager)

任务管理器便是主要用来协调系统外呼时的任务生成、刷新、保存等工作的。任务管理器持有一个任务队列,其中存放着将要进行处理的各种任务对象,通道借助GetNextTask来获取待处理的任务实例,并在完成后由任务本身将处理结果保存起来。同时,通过一个填充器从系统外部不断的向队列中加入符合条件的任务。

如下图所示:

(图4.1 任务管理器结构)

可以看出只要实现了ITaskManager接口都可以作为任务管理器使用,为此你可以实现灵活的结构如当发布试用版时只是从一般的文本文件加载任务,而正式版可以从数据库、外部程序或Web服务加载。你只需将对应的任务管理器发送给用户即可,根本不用再重新编译你的系统就可实现。

ITaskManager的接口定义如下:

(图4.2 任务管理器接口)

4.1.1、任务队列填充器(TaskQueueFillter)

接口并不关心在具体实现中如何保存任务队列,也没有实现如何加载任务的,其实从图22可以看出这些工作全部都交给了任务填充器ITaskQueueFillter对象来完成。任务填充器知道从哪里加载、如何加载这些任务的。

(图4.3 队列填充器接口)

(图4.4 任务管理器基类)

从基类的定义中可以看到一个比较关键的内部成员-FTimer(定时器),在任务器缴活后通过接口定义的队列直译间隔时间系统将会自动调用填充器填充任务队列。除此之外也可拥有第二种选择,那就是在OnTimer事件中自行处理队列填充任务,当然这并不是我们首选的填充方式。

4.1.2、任务(Task)

在不同的应用环境中可能需要完成不一样的工作,那么如何来适应这种需求呢?在此我们也采用了一个窄接口对任务进行了简单的定义,其中最重要的StoreTask方法,它只需知道如何保存自己就可以了,而在具体的应用中可以根据需要进行扩充。从前面的图示可以知道你的脚本理解目前处理的是什么类型的任务,脚本引擎透过StoreTask方法即可完成对任务处理结果的保存工作,因此满足以下接口的任务即可实现相应的外呼要求啦。

任务接口如下图所示:

(图4.5 任务接口)

4.1.3、会话(Session)

在一次呼叫的过程中往往需要有许多状态或相关的细节需要进行记录,以便日后可以查询。如在催缴工作中从系统开发拨号到最终系统/对方挂机,这期间都执行了哪些业务流程且结果是怎样的?如果将其全部放在脚本引擎中进行处理的话,这部分工作将是十分繁重的。为此,在LightweightCTI的适配器层我们提供了会话服务,通过会话服务这一切将变得十分地简单。以下是会话服务的接口:

(图4.6 会话服务接口)

4.2、临界区服务(CriticalSection)

在LightweightCTI开发的初期,在编码与测试中因时间比较紧张所以并没有注意到板卡驱动对多线程是否能够很好的进行支持的问题。临到最后进行系统测试时才发现东进的D系列模拟卡驱动程序本身并不支持多线程,当一个通道有振铃或按键等信号时,运行于其它线程的通道同样也接收到相同的信号,为了解决这个问题曾经花费我一天的时间对代码从头到尾又重新检查一遍,可是依然没有解决问题。后来经朋友指点及向东进技术支持询问才发现问题是出在多线程上。由于线程本身的无序性,所以在启动通道执行业务逻辑后,各通道就会可能在任意时刻同时访问驱动接口(API),又由于东进的DBDK开发包底层就不支持线程模式,所以造成了程序本身的混乱。而采用论询的方式因其本身在同一时刻只有一个通道访问API资源,因此,便不会出现上述情况。那么又如何解决这个问题呢?

而临界区则是在Windows下防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注。临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在修改全局数据(如集合类)时会使用临界区。事件、多用户终端执行程序和信号量也用于多线程同步,但临界区与它们不同,它并不总是执行向内核模式的控制转换,这一转换成本昂贵。要获得一个未占用临界区,事实上只需要对内存做出很少的修改,其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性的缺点在于临界区只能用于对同一进程内的线程进行同步。居于上述考虑及LightweightCTI框架的特点,我们选择使用临界区来进行线程同步操作,下面是对临界区对象的定义:

(图4.7 临界区对象定义)

那么各个通道线程又是如何来使用临界区的呢?因系统中可能存在着多处需要使用临界区的地方,而在设计时又是无法确定的,所以我们使用了一个临界区对象列表,当有需要时向ChannelManager申请,如果是第一次申请则创建一个临界区对象并放入列表中,以后就可以反复使用啦,待系统退出系统时由ChannelManager进行统一清除,做到了简洁与灵活的较好结合。

请看下面第一次使用临界区的代码片段:

(图4.8 使用临界区代码片段)

系统首先根据临界区名称获取对应的临界区对象,如果不存在则创建之,否则直接进入临界区。

获取临界区的代码如下:

(图4.9 获取临界区代码)

这样其它对象在使用临界区对象时就十分方便了。

(图4.10 使用临界区对象)

注意:在以上的代码中有一个try…finally块将实际的代码包裹了起来,而在finally块内则是FChannelManager.LeaveCS(D160XCS),这主要是为了保证线程在申请使用临界区对象后能够及时的释放临界区对象内部的LockCount 与 RecursionCount 字段中分别包含其初始值 -1 和 0,这一点非常重要,不然的话将会造成临界区下线程的死锁。

4.3、日志服务(Log Service)

对于日志服务我不想重复其作用,而是想说说日志给我们的开发工作带来的影响,或许我们对于在集成开发环境中进行程序调试无比熟悉,无论是断点的设置还是变量的检查,真的是十分方便。而一旦离开了集成环境我们又怎样来调试和判断程序中的错误呢?对,通过系统日志为我们提供服务。当程序已经部署到客户应用环境中后,就不可能再有机会让你像集成环境中那样调试和判断程序中的问题了,但是程序中输出的调试日志信息同样可以帮我们解决上述问题,如系统运行到某一时刻输入的状态量等,用户通过将日志信息发回给项目组,开发人员将可以依据日志信息判断程序中出现了哪些问题,这便是日志驱动的软件开发。

  日志服务接口:

  

(图4.11 日志服务接口)

利用日志服务不旦可以详尽的记录系统中发生的每一件事,而且可以实现将日志存放到文本文件、数据库、Windows日志等目的地中去,下面是LightweightCTI默认日志组件的定义,将纯文本文件作为日志保存的地点,同时实现了日志文件保存级别的功能。

如下图所示:

(图4.12 系统默认日志组件)

4.4、XML解析服务(XMLParse Service)

为能够在配置文件中保存复杂的配置信息,LightweightCTI使用XML作为配置文件的保存格式,通过配置路径的设置不但可以将其作用独立的配置文件进行保存,也可以融入到其它配置文件中,如集成到IVR系统、呼叫中心等上层应用软件的配置信息内部,保证了系统的扩展性。

(图4.13 简单的XML解析组件)

五、适配器层整体结构图

   

(图5.1 适配器层整体结构图)

在LightweightCTI适配器层由如下类构成:

Ø         通道管理器(ChannelManager);

Ø         板卡适配器(CTICardDriver);

Ø         通道(Channel);

Ø         脚本引擎(ScriptEngine);

Ø         任务管理器(TaskManager);

Ø         呼叫任务(CallTask);

Ø         会话(Session);

Ø         任务队列填充器(TaskQueueFillter);

Ø         TTS(适配器)引擎(TTSEngine);

Ø         临界区(CriticalSection)。

六、应用LightweightCTI的软件结构

前面对LightweightCTI的体系结构进行了详细的介绍,我们已经知道其灵活性及强大的扩展性。但是再强大、再灵活的框架如果不好使用的话,那也没有任何价值。为此,在本节将对使用LightweightCTI进行应用开发的软件结构进行简要的介绍。

6.1、框架整体设计模式

为了便于LightweightCTI本身的开发以及使用它进行语音应用软件的开发,在设计之初始我们就对其进行了相应的规划。无论是LightweightCTI核心系统还是外围的扩展组件都遵循MVC(模型-视图-控制器)的设计模式。

(图6.1 适配器层应用开发模式)

通过MVC模式的应用能够使控制器、视图对模型中数据的变化做出相应的响应,同时又减小了系统中各组件之间的依赖关系。也有利于对系统进行单元测试,因为对模型中的数据(业务逻辑)的操作根本不涉及视图,所以有机会单独的对模型进行测试,当然它的好处绝不仅于此,这方面的资料十分多大家可以找来详细了解一下这里便不再详述。

6.2、应用LightweightCTI的软件结构

在面向对象的软件开发与应用中,良好的层次结构不但有利于软件系统的开发与维护工作,更能够提高系统相关组件的复用程度,从而实现构件化的软件开发。LightweightCTI的设计目标之一就是实现组件化的应用软件结构,对此在LightweightCTI体系结构一节有详细的介绍,在此将只对应用LightweightCTI进行应用软件开发的结构图作一简要介绍。

操作系统、数据库软件

板卡驱动程序软件

其它软件如CRM、ERP

公共服务、相关工具等

全局属性、资源

组件容器

可扩展插件树

呼叫中心、800电话服务系统、娱乐声讯台等应用软件

IVR流程编辑器

IVR流程调试器

IVR脚本编辑器

板卡适配器层

动态脚本引擎

IVR流程引擎

(图6.2 应用LightweightCTI的软件结构)

从上图可以看出利用LightweightCTI所开发的软件结构层次主要有:(1)、底层的操作系统、数据库系统、其它软件如CRM、ERP等及语音卡等板卡驱动程序软件;(2)、LightweightCTI框架微内核系统,此层向上层提供基础的全局属性、资源管理、系统扩展组件的管理等功能;(3)、系统扩展组件,这一层次包括提供基础服务的板卡适配器层、动态脚本引擎、IVR流程引擎及稍上层次的IVR流程编辑器、调试器和脚本编辑器;另外一方面是具体的业务软件,如呼叫中心、客服800系统、娱乐声讯软件等。整个系统由系统组件容器接口进行统一的管理与调度,最终使插入到其中的各扩展组件相互协作完成特定的业务功能。

七、LightweightCTI发展规划

应用LightweightCTI框架为东莞市自来水公司开发的自动催缴与查询系统已经成功部署并稳定运行一个多月时间。因此,我们对于运用LightweightCTI框架作为商业软件的开发是充满信心的。另外一方面,由于本人专业技术水平的限制,在文中难免出现这样那样的错误,而框架本身也有待进一步完善和丰富,为此请各方面的专家就文中出现的错误及框架设计不完善之处予于斧正。在接下来的时间里我将就大家提供的反馈意见对其进行修正。在中长期的发展中,我将在资源允许的条件下着重完成如下工作:

(1)、对国内目前主流的板卡适配器的开发,当然有些受到开发环境搭建的限制将会推迟完成;

(2)、为LightweightCTI for Delphi/C++Builder 1.0.0.0创建可扩展的微内核系统;

(3)、研究VoiceXML RFC文档,今后无论哪个版本的框架将以VoiceXML作为首先支持的脚本语言;

(4)、将LightweightCTI移植到.NET 2.0环境下,并以其作为后续版本主要的开发环境;

语音卡开发系列: LightweightCTI架构设计(作者:东成西就)相关推荐

  1. 【成为架构师课程系列】架构设计中的核心思维方法

    架构设计中的核心思维方法 目录 前言 #一.抽象思维 #二.分层思维 #三.分治思维 #四.演化思维 #五.如何培养架构设计思维

  2. 分布式发号器架构设计

    版权声明: 本文为博主原创文章,未经博主允许不得转载.关注公众技术汇(ID: jishuhui_2015)可联系到作者. 一.需求介绍 1.分布式环境下,保证每个序列号(sequence)是全系统唯一 ...

  3. 架构设计 | 分布式体系下,服务分层监控策略

    本文源码:GitHub·点这里 || GitEE·点这里 一.分布式故障 分布式系统的架构,业务开发,这些在良好的思路和设计文档规范之下,是相对来说好处理的,这里的相对是指比较分布式架构下生产环境的突 ...

  4. 架构设计 | 基于Seata中间件,微服务模式下事务管理

    源码地址:GitHub·点这里 || GitEE·点这里 一.Seata简介 1.Seata组件 Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata将为用 ...

  5. 数据仓库系统的技术体系架构设计

    数据仓库系统的技术体系架构设计 作者:成晓旭 该数据仓库系统的主要功能是从众多外部系统中,采集相关的业务数据,集中存储到系统的数据库中.系统内部对所有的原始数据通过一系列处理转换之后,存储到数据仓库的 ...

  6. 语聊房app源码及架构设计

    语音社交产品技术架构设计 语音社交产品的技术架构设计是产品开发中非常重要的一环.在设计时需要考虑产品的功能.性能.可靠性等多个方面,同时也需要与产品策划与设计相协调.以下是语音社交产品技术架构设计的主 ...

  7. SLAM+语音机器人DIY系列:(二)ROS入门——2.ROS系统整体架构

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人"miiboo"中的大部分程序也采用ROS进行开发,所 ...

  8. .NET企业级应用架构设计系列

    一..NET企业级应用架构设计系列之技术选型 这里说的技术选型实际上是指技术方向的选择,或者叫平台方案的选择,也或者叫技术路线等,总之是大方向的把握.假定项目背景是要做一个中型WEB系统,公司组建新的 ...

  9. .NET企业级应用架构设计系列之应用服务器

    本文属spanzhang(张友邦)原创,发布地址为:http://blog.csdn.net/spanzhang.转载或引用请注明原文之出处,谢谢! .NET企业级应用架构设计系列之开场白 .NET企 ...

最新文章

  1. nsis帮助文档_使用NSIS打包程序
  2. CoGAN pytorch
  3. 阿里云maven镜像地址
  4. IIR+全通滤波器实现相位平衡_matlab仿真
  5. CAD安装失败怎样卸载重新安装CAD,解决CAD安装失败的方法总结
  6. java上传文件到ftp_java实现文件上传下载至ftp服务器
  7. 魅族15系统是android,魅族15系列评测:性能够用王者荣耀优化
  8. liteide无法自动补全代码问题解决【go: cannot find GOROOT directory: c:\go】
  9. 火狐浏览器配置webDriver
  10. 联想Yoga C930 NM-B741 EYG70 Ariel-SVT笔记本点位图TVW
  11. 读npy、pck、nii格式数据集
  12. windows自动更新导致:无法访问网络位置*:\Program Files(x86)
  13. 【001】Zabbix学习笔记-Zabbix简介与部署
  14. BeautifulSoup总结及contents内容分析
  15. C++核心准则边译边学-I.4 接口类型应该精准且严格
  16. 从零开始:微信小程序零基础入门宝典
  17. 2020PAKDD 阿里巴巴智能运维算法大赛TOP20 ——磁盘故障预测问题比赛思路、难点与问题总结
  18. 软件原型设计(软件Axure_RP)
  19. thinking_in_java_version_1
  20. [论文阅读] (ASONAM2019) Meta-GNN: 属性异构网络中用于半监督学习的元图神经网络

热门文章

  1. QQ每天定时领取群礼物
  2. SQL Server 2012 随时随地管理数据
  3. 黑莓如何装java软件_安装BlackBerry的Java开发环境
  4. 可转债券、质押式回购、买断式回购
  5. 使用git进行项目版本管理
  6. 天轰穿典型多层架构留言本项目实战免费下载
  7. 三国杀(1):VS2017 C/C++ lua tolua++ 编译 集成,及使用介绍
  8. 全志R16_Camera支持列表芯片资料(Allwinner R16 Camera Support List)
  9. MacBook 苹果笔记本 下载Xcode历史版本
  10. 01500105_MLDN-魔乐科技-李兴华【Java核心技术】_JDBC连接Oracle数据库