本文要讲的是一个基于WCF的一个轻量级但很实用的模块服务框架及服务托管程序MSH,包括几个常见应用场景的分析,目录如下:
一、WCF Module Framework介绍与讲解
二、Module Service Host(MSH)介绍与讲解
三、常见应用场景分析
四、后期功能加强
五、开源计划
下载MSH1.0 (for .net 3.5)
下载最简单的MshDemo (for vs2010 & .net 3.5)
这只是开篇,后续将会发布开发模块实例和应用相关的实例文章,请关注。
一、WCF Module Framework介绍与讲解
面向服务架构(Service-Oriented Architecture-SOA)在IT领域变得越来越重要,其中Windows Communication Foundation(WCF)是由微软的一个面向服务架构,从功能的角度来看,WCF已经可以看作是ASMX,.Net Remoting,Enterprise Service,WSE,MSMQ等技术的并集。还不了解什么是WCF的朋友请到百科查看资料。
那么,WCF模块服务框架(WCF Module Framework)是什么呢?它是本人为目前公司写的一个基于WCF的轻量级但很实用的框架(处于KudySharp的命名空间KudySharp.Frameworks.WcfModuleFramework)。目前版本支持的主要功能有:
1、支持WCF的路由中转服务;
2、支持自定义通道上下文。利用通道上下文可以方便地在调用WCF服务时传递额外数据,使客户端与服务器端的交互更加灵活;
3、支持动态加载和卸载模块服务,利用这个特点,你可以快速部署并使用你的WCF服务;
4、支持模块服务被托管和调用时进行令牌的验证;
5、支持每个模块的自定义通道安全验证,还可以细化到每个服务方法;
6、支持不同模块服务使用不同的通道上下文。
请看下面的客户端调用模块主机的请求处理简单流程图解(MSH是下节会讲到的WCF模块服务托管程序):
这个图意思是:框架上托管了N个模块服务,且每个模块服务里有一个或多个可用的服务,当接收到客户端对某个模块服务发出请求后,框架会先验证此模块服务使用的通道上下文,如果存在,则验证模块的令牌,如果令牌是合法的,则执行些模块服务的自定义通道验证方法,如果验证通过了,最后才返回要调用的服务给客户端。那么在启用路由的情况呢?也很简单,例如客户端要通过主机A调用主机B上的服务,那么主机A只是简单把请求转发给主机B,然后主机B再完成上面所说的请求流程,然后把服务返回给主机A,最后返回给客户端。
下面对WCF Module Framework中主要的成员的讲解(只列出开发模块时所要了解的成员,处于KudySharp的命名空间KudySharp.Frameworks.WcfModuleFramework)。
1.模块主机(基)类ModuleHost,这是实现自定义模块主机需要继承的基类。
RoutingEnabled:是否开启路由,如果不需要路由服务,请重写它并返回false,默认是开启的。
TokenResolver:令牌处理器,默认为:NullTokenResolver,不对令牌进行验证。强烈建议实现自己的令牌处理器,实现接口ITokenResolver或者继承TokenResolverBase<TResolver>类。
PortConfigManager:端口配置管理器,默认为:XmlPortConfigManager,采用xml配置。如果有需要,可以实现自己的端口配置管理器,实现接口IPortConfigManager即可。
GetChannelContextTypesForRoutingService():获取需要提供路由服务的通道上下文类型,这个方法是为了需要只用独立服务器来提供路由服务而设计的,默认返回null,重写此方法返回你自己定义的通道上下文类型即可,例如:return new List<Type>{ typeof(MyChannelContext) };;另外,下面提到的模块基类使用的通道上下文不需要在这里返回,因为如果启用了路由时框架会自动为所有的模块所使用的通道上下文打开路由服务。
// 模块主机(基)类 public class ModuleHost : IModuleHost { /* 属性 */ // 是否已加载 public bool IsLoaded { get; } // 是否开启路由 public virtual bool RoutingEnabled { get; } // 模块存放目录 public virtual string ModuleDirectory { get; } // 正在工作模块列表 public IDictionary<string, WorkingModule> WorkingModules { get; } // 令牌处理器,默认为:NullTokenResolver public virtual ITokenResolver TokenResolver { get; } // 端口配置管理器,默认为:XmlPortConfigManager public virtual IPortConfigManager PortConfigManager { get; } /* 主要方法成员 */ // 加载 public void Load(); // 加载核心的实现 protected virtual void LoadCore(); // 卸载 public void Unload(); // 卸载核心的实现 protected virtual void UnloadCore(); // 获取需要提供路由服务的通道上下文类型 public virtual IList<Type> GetChannelContextTypesForRoutingService(); /* 主要事件 */ // 主机加载后触发 public event EventHandler Loaded; // 主机正在加载时触发 public event EventHandler Loading; // 主机卸载后触发 public event EventHandler Unloaded; // 主机正在卸载后触发 public event EventHandler Unloading; // 模块加载失败时触发 public event EventHandler<ModuleLoadFailedEventArgs> ModuleLoadFailed; // 模块状态发生改变时触发 public event EventHandler<ModuleStateChangedEventArgs> ModuleStateChanged; // 工作模块被添加后触发 public event EventHandler<WorkingModuleAddedEventArgs> WorkingModuleAdded; // 工作模块被移除后触发 public event EventHandler<WorkingModuleRemovedEventArgs> WorkingModuleRemoved; /* 执行以上主要事件的默认方法 */ protected virtual void OnLoaded(object sender, EventArgs e); protected virtual void OnLoading(object sender, EventArgs e); protected virtual void OnUnloaded(object sender, EventArgs e); protected virtual void OnUnloading(object sender, EventArgs e); protected virtual void OnModuleLoadFailed(object sender, ModuleLoadFailedEventArgs e); protected virtual void OnModuleStateChanged(object sender, ModuleStateChangedEventArgs e); protected virtual void OnWorkingModuleAdded(object sender, WorkingModuleAddedEventArgs e); protected virtual void OnWorkingModuleRemoved(object sender, WorkingModuleRemovedEventArgs e); }
2.令牌处理器接口ITokenResolver,这是实现自定义令牌处理器需要实现的接口,也可以继承TokenResolverBase<TResolver>基类。
// 令牌处理器接口 public interface ITokenResolver { // 生成模块令牌 string GenerateToken(string serviceName); // 验证模块令牌 bool VerifyToken(string serviceName, string tokenToVerify); }
3.端口配置管理器接口IPortConfigManager,这是实现自定义端口配置管理器需要实现的接口,也可以继承XML端口配置管理器XmlPortConfigManager。
// 端口配置管理器接口 public interface IPortConfigManager { // 获取服务端口 IList<int> ServicePorts { get; } // 根据端口获取配置 NameValueCollection GetConfig(int port); // 根据端口保存配置 void SaveConfig(int port, NameValueCollection configs); // 判断配置项是否合法 bool IsConfigItemValid(string name, string value, out string errorMessage); }
// XML端口配置管理器(基)类 public class XmlPortConfigManager : IPortConfigManager { // 获取配置目录 public virtual string ConfigDirectory { get; } // 获取服务端口 public IList<int> ServicePorts { get; } // 根据端口获取配置 public NameValueCollection GetConfig(int port); // 根据端口保存配置 public void SaveConfig(int port, NameValueCollection configs); // 判断配置项是否合法 public bool IsConfigItemValid(string name, string value, out string errorMessage); }
了解上面的3个成员,就可以开发自己的模块主机了,下面继续看开发自己的模块服务需要了解的成员:
4.通道上下文基类ChannelContextBase<TContext>,这是实现自定义通道上下文需要继承的基类,细心的你应该可以看到此类是继承基类ChannelContextBase的,ChannelContextBase只是实现框架功能的一个过渡类,所以大家只要关心ChannelContextBase<TContext>就可以了,另外,通道上下文和属性都必须是可序列化的,请不要忘了加上Serializable特性。
ContextHeaderLocalName/ContextHeaderNamespace:建议返回自己定义的头部信息的LocalName和Namespace。
ReturnMessage:返回信息,一般是指服务器端返回到客户端的信息,服务器端发生错误时会把错误信息保存在此属性,以供客户端使用。
this[string key]:通过key获取或设置上下文数据,这是为方便大家在继承上下文基类时没有定义具体的属性时,可以通过MyChannelContext.Current["mykey"]来设置或访问额外的通信数据。
国际时间相关 和 服务器端相关 的成员就不多解释了,后面的实例文章会一一说明。
// 通道上下文基类 [Serializable] public abstract class ChannelContextBase<TContext> : ChannelContextBase where TContext : ChannelContextBase<TContext>, new() { /* 头部信息 */ // 头部信息的LocalName protected internal virtual string ContextHeaderLocalName { get; } // 头部信息的Namespace protected internal virtual string ContextHeaderNamespace { get; } /* 基本属性 */ // 获取当前通道上下文实例 public static TContext Current { get; set; } // 返回信息(服务器端返回到客户端的信息) public string ReturnMessage { get; set; } // 通过key获取或设置上下文数据 public object this[string key] { get; set; } /* 国际时间相关 */ // 时区 public TimeZoneInfo TimeZone { get; set; } // 根据TimeZone属性转换本地时间为Utc时间 public DateTime ConvertTimeToUtc(DateTime dateTime); // 根据TimeZone属性从Utc时间转换本地时间 public DateTime ConvertTimeFromUtc(DateTime dateTime); /* 服务器端相关(客户端调用无效) */ // 获取当前服务端口 public int GetServicePort(); // 获取当前模块服务名称 public string GetServiceName(); // 获取当前服务端口的配置 public NameValueCollection GetConfig(); // 获取当前请求的原始客户端IP地址 public string GetRemoteEndpointAddress(); // 获取当前请求的客户端IP地址(可指定是否原始客户端) public string GetRemoteEndpointAddress(bool original); }
5.模块基类ModuleBase<TContext, TContract, TService>,这是开发自定义模块需要继承的基类,TContext是此模块使用的通道上下文,TContract是WCF接口约束,TService是实现TContract接口约束的服务类。
Token:获取令牌,默认返回ServiceName,具体请返回模块主机里TokenResolver根据ServiceName生成的令牌。
ServiceName:获取服务名称,默认返回此模块类的类名称,建议返回具体有意义的名称(如:MyService),要注意的是同一个模块主机上的每个模块服务名称必须是唯一的,上面的Token也必须是唯一的。
ServiceDescription:获取服务描述,默认返回空的字符串,这个属性可有可无。
ChannelVerifier:获取通道验证器,默认为:NullChannelVerifier,实现自定义的通道验证器,强烈建议实现自己的通道验证器,实现接口IChannelVerifier即可。
CreateNetTcpBinding():获取打开服务使用的NetTcpBinding,如果需要使用自定义的NetTcpBinding实例,请重写此方法。
// 模块基类 public abstract class ModuleBase<TContext, TContract, TService> : IModule where TContext : ChannelContextBase<TContext>, new() where TService : TContract, new() { /* 基本属性 */ // 获取令牌 public virtual string Token { get; } // 获取服务名称 public virtual string ServiceName { get; } // 获取服务描述 public virtual string ServiceDescription { get; } // 获取模块状态 public ModuleState State { get; } // 获取通道验证器,默认为:NullChannelVerifier public virtual IChannelVerifier ChannelVerifier { get; } /* 基本方法 */ // 打开模块 public bool Open(); // 关闭模块 public bool Close(); // 模块被框架后 public virtual void OnLoad(); // 模块被卸载后 public virtual void OnUnload(); // 获取打开服务使用的NetTcpBinding public virtual NetTcpBinding CreateNetTcpBinding(); // 获取打开服务时需要添加的EndpointBehavior public virtual IList<IEndpointBehavior> CreateEndpointBehaviors(); /* 主要事件 */ // 正在打开时触发 public event EventHandler Opening; // 已经打开时触发 public event EventHandler Opened; // 正在关闭时触发 public event EventHandler Closing; // 已经关闭时触发 public event EventHandler Closed; // 失败时触发 public event EventHandler Faulted; /* 执行以上主要事件的默认方法 */ protected virtual void OnOpening(object sender, EventArgs e); protected virtual void OnOpened(object sender, EventArgs e); protected virtual void OnClosing(object sender, EventArgs e); protected virtual void OnClosed(object sender, EventArgs e); protected virtual void OnFaulted(object sender, EventArgs e); }
6.通道验证器接口IChannelVerifier,这是实现自定义通道验证器需要实现的接口,也可以继承通道验证器接口基类ChannelVerifierBase<TVerifier>。
// 通道验证器接口 public interface IChannelVerifier { // 验证通道 VerifyChannelResult VerifyChannel(object contextState, Message message); }
了解上面的3个成员,就可以开发自己的模块服务了,下面继续看调用自己的模块服务需要了解的成员:
7.模块客户端类ModuleClient,这是客户端调用模块服务的助手类,使用很简单,就不多说了。TModule是要调用模块,TChannel是TModule中的WCF接口约束TContract,方便在action里使用接口的方法。
// 模块客户端类 public static class ModuleClient { // 创建通道:没返回值,使用TModule创建的NetTcpBinding public static void CreateChannel<TModule, TChannel>(string server, int port, Action<TChannel> action, params string[] routingServers) where TModule : IModule, new(); // 创建通道:有返回值,使用TModule创建的NetTcpBinding public static TResult CreateChannel<TModule, TChannel, TResult>(string server, int port, Func<TChannel, TResult> action, params string[] routingServers) where TModule : IModule, new(); // 创建通道:没返回值,使用自己定义的NetTcpBinding,如果为null则使用TModule创建的NetTcpBinding public static void CreateChannel<TModule, TChannel>(string server, int port, NetTcpBinding binding, Action<TChannel> action, params string[] routingServers) where TModule : IModule, new(); // 创建通道:有返回值,使用自己定义的NetTcpBinding,如果为null则使用TModule创建的NetTcpBinding public static TResult CreateChannel<TModule, TChannel, TResult>(string server, int port, NetTcpBinding binding, Func<TChannel, TResult> action, params string[] routingServers) where TModule : IModule, new(); }
如何使用?
ModuleClient.CreateChannel<TModule, TChannel>(server_A, port, proxy => { //use service of server_A //proxy.hellowork(); }); ModuleClient.CreateChannel<TModule, TChannel>(server_A, port, proxy => { //use service of server_D //proxy.hellowork(); }, server_B, server_C, server_D);
如果你认真的把上面的成员都看了,相信你已经知道怎么使用此框架了,下面继续看下一节。
二、Module Service Hosting(MSH)介绍与讲解
上面所讲的框架已经实现了,那我们总需要一个程序来管理自己写的模块服务吧?所以为了节省大家的时间和更加方便地使用此框架,本人还简单的写了一个基于WCF Module Framework的模块服务托管主程序,名称为:Module Service Hosting(MSH)。
Module Service Hosting(MSH),是一个WCF服务模块托管程序,它提供了常用功能来管理自定义的模块服务,有了它,你可以快速开发和部署自己的WCF服务,使各个应用程序之间的沟通更方便。
MSH主程序相关文件:
目录说明请看说明文件夹里的文件。
下面对MSH的主要界面作介绍:
1.主界面
2.配置管理
3. 令牌生成
4.运行检查
以上是同一个界面,因为添加Window任务时需要管理员权限,当以管理员身份运行MSH时,显示的是第一个界面,否则显示第一个界面,要求输入用户名和密码才可以执行。
三、常见应用场景分析
下面只简单的介绍场景的应用例子,在后继的文章中会提供各种的实例及源码给大家参考。
应用场景一:
此场景的应用例子(一):某公司有多台服务器需要维护、监控异常信息,这时可以在每台服务器上部署MSH,然后用一台服务器A充当MSH客户端,用WEB系统来调用MSH上的模块服务,MSH配置只允许服务器A调用服务,具体是什么服务就要看维护什么、监控什么了。
此场景的应用例子(二):某游戏公司开发的一款游戏,分了N个游戏区,第个区的数据都是独立分开的,需要开发一个GM系统来管理所有游戏区的业务数据,这时,可以在每台服务器上部署MSH,然后用GM服务器A充当MSH客户端,根据不同的服务器来调用模块服务来管理业务数据,具体什么数据当然要看游戏的业务了。(本人之前游戏公司就采用了此方案)
应用场景二:
此场景的应用例子(一):某公司需要用多台服务器来实现分布式缓存,给A系统提供数据,这时可以在每台服务器上部署MSH,然后A系统充当MSH客户端,调用MSH上的模块服务,MSH配置只允许A系统所在服务器调用服务,具体是什么服务就是缓存数据的读和写了,直接用.net里的缓存类实现都可以,重点是节点定位,这个需要根据具体的策略和算法来确定调用哪台服务器的服务。(这只是个方案例子,一般分布式缓存都习惯了用memcached的方案,至少我知道很多公司是这样)
此场景的应用例子(二):某公司需要某个服务实现负载均衡集群,给A系统提供服务,这时可以在每台服务器上部署MSH,然后A系统充当MSH客户端,调用MSH上的模块服务,MSH配置只允许A系统所在服务器调用服务,节点定位和上一例子一样,根据具体的策略和算法来确定调用哪台服务器的服务。
应用场景三:
应用场景三和应用场景二差不多,只是把一台服务器公开对外,提供路由中转服务,真正提供服务的是内部局域网上的N台服务器,例子就不多讲了。
四、后期功能加强
此WCF模块框架目前的初版本已经可以满足基本的功能需求,后期要考虑会有服务工作流,自动更新,及更多灵活的扩展性等,同时欢迎大家也提出一些宝贵意见。
五、开源计划
KudySharp类库作为本人公司重点类库之一,不会公开源码,请不要索取。MSH程序在后期完善功能后会公布MSH的源码, 具体时间不好定,哪个公司如果现在需要MSH的源码,请使用公司邮箱发个邮件到kudychen#gmail.com
本软件可以个人或企业免费使用,喜欢MSH的朋友别忘了顶一下哦:)
免责声明:
1.本软件及所附带的文件是作为不提供任何明确的或隐含的赔偿或担保的形式提供的。
2.用户出于自愿而使用本软件,您必须了解使用本软件的风险,我们不承担任何因使用本软件而产生问题的相关责任。