【转载】企业级服务器设计与实现经验之插件系统--功能插件 .
在描述功能插件的外貌之前,先有必要了解这个被功能插件处理的数据流,这个数据流可能基于某种流协议格式,也可以是被序列化的请求对象。所谓“基于格式的流协议”是这样一种消息协议,它定义了数据流的固定偏移处接下来的几个字节的含义,比如规定数据流从偏移为4的地方是一个整数,该整数表示本消息的长度。如果采用基于协议格式的流,则功能插件必须首先能解析此数据流;如果采用的是序列化的流,则只要反序列化该流即可(序列化一般也可以采用两种方式--二进制序列化或XML序列化)。可以这么认为,序列化流是协议格式流的一种特例。
什么情况下需要使用什么样的数据流来传递消息了?一般,如果通信的双方(通常是服务器端和客户端)是同构平台,则可以很方便的使用二进制序列化的方式,因为这种方式避免了基于字节解析的繁琐。当都在.net或java平台时,由于类库本身就提供了序列化和反序列化组件,所以使用序列化的方式特别方便。如果是异构平台,可能就要使用基于格式的流协议或者使用XML的序列化方式。一般低级语言(如C,C++)和大多数高级语言(如C#)都能基于字节进行操作。有很多这样的情况,服务器平台是.net或java平台,而终端是嵌入式设备,如手机、PDA,终端通常使用C或C++语言,这样的异构平台之间可能就需要使用基于格式的流协议。对于C/C++来说,解析格式流的效率是很高的,而使用JAVA或C#就要吃力些,但终究可以保证双方的正常通信。
接下来,我们可以看看功能插件IFunAddin的接口,功能插件首先需要实现功能接口IFunction,其定义如下:
/// IFunction 功能组件基础接口
/// 作者:朱伟 sky.zhuwei@163.com
/// </summary>
public interface IFunction
{
bool Initialize(string configPath ,object arg) ; //arg一般为null ,如果功能插件需要主动发布消息,则接口可通过arg传入
void Uninitialize() ;
//此接口中request_stream是一个单个的完整请求
byte[] DealRequestMessage(byte[] request_stream ) ;//同步回复
}
其中Initialize方法的configPath指明了该插件应当在何处获取所需的配置信息。有的功能插件可能需要连接到数据库,而数据库的ip,帐号密码就可以放在configPath所指向的配置文件中。IFunction接口中最重要的方法是DealRequestMessage方法,该方法接受一个请求数据流,然后返回一个应答数据流。你也许会问,为什么不接受一个请求对象返回一个应答对象,而是流了?我们都知道,来自网络的请求的原始形式就是数据流,将数据流解析为对象应当是功能插件自己的事情,难道还有一个地方比功能插件更了解它自己所要处理的请求和给出的回复是什么样子吗?所以,消息的解析模块应当在功能插件的内部!
功能插件不仅要实现功能接口,为了表现其插件的特征,它还必须实现插件的基础接口IAddin和插件配置接口IAddinConfig,所以其完整的外貌如下:
/// IFunAddin 功能插件基础接口
/// </summary>
public interface IFunAddin : IFunction , IAddin ,IAddinConfig
{
}
关于IAddin接口和IAddinConfig接口,在前面的文章已经有过介绍,此处不在重复。从DealRequestMessage方法的签名看来,其是在基于流的方式操作请求和回复消息,基于流的方式操作数据的优点是效率高,但缺点是代码的可读性差,并且难于维护。所以我考虑将基于流的方式转化为基于对象的方式进行操作。为了对这一转化进行支持,我给出了从IFunAddin接口继承的抽象基类BaseFunAddin,BaseFunAddin抽象类为IFunAddin接口的DealRequestMessage提供了参考实现,在这个实现中的核心部分就是将基于流的操作方式,转换为基于对象的操作方式,其完整实现如下:
{
if(this.serviceStopped)
{
return ProtocalServiceFixture.GetRespondStreamWhenFailure(ServiceFailureCause.ServiceStopped ,requestStream) ;
}
IRequestMessage i_requestMsg = this.ParseRequestStream(requestStream) ;
if(i_requestMsg == null)
{
return ProtocalServiceFixture.GetRespondStreamWhenFailure(ServiceFailureCause.ParseFailure ,requestStream) ;
}
IRespondMessage i_respondMsg = this.HandleRequestMsg(i_requestMsg) ;
if(i_respondMsg == null)
{
return ProtocalServiceFixture.GetRespondStreamWhenFailure(ServiceFailureCause.HandleFailure ,requestStream) ;
}
return i_respondMsg.ToByteStream() ;
}
可以看到我们引入了请求消息和回复消息两个接口,这两个接口定义如下:
{
string GetRequestMessageType() ;
}
public interface IRespondMessage
{
byte[] ToByteStream() ; //转化成标准的回复流(可通过格式填充实现,也可通过序列化的方式实现)
}
IRespondMessage接口定义了一个ToByteStream方法,这是将对象转换为流所必须的方法。另外,BaseFunAddin的DealRequestMessage方法对失败的情况做了处理。
为了实现对基于对象操作方式的支持,BaseFunAddin引入了另外两个虚方法,它们是:
protected abstract IRespondMessage HandleRequestMsg(IRequestMessage i_requestMsg) ; //处理失败返回null
ParseRequestStream方法用于将请求流解析为请求对象,如果请求流是序列化的对象,ParseRequestStream只要执行反序列化即可,如果请求流是基于格式定义的,则ParseRequestStream的动作就要复杂繁琐许多,它需要从请求流中取出一个个元素,然后把这些元素的值填充到请求对象对应的字段中。HandleRequestMsg方法是完全基于对象的方式处理请求,它接收一个请求对象,返回一个回复对象。所以一个具体的功能插件只要从BaseFunAddin继承,并给出ParseRequestStream方法和HandleRequestMsg方法的实现就可以了。BaseFunAddin采用的就是所谓的模板方法模式。
功能插件的主要内容就是这些,疏漏的细节会在后面补充进来。下期我将介绍通信插件,通信插件的内容会更精彩!
邮箱:steven9801@163.com
QQ: 48039387