框架介绍
KJFramework框架中内置了一套轻量级RPC服务调用框架,使用此框架,将使得用户不再关心底层协议转换和网络传输的繁琐过程,解放程序员的脑力劳动。
此框架的使用也非常简单,作为服务器端的使用人员,只需要完成一个契约的实现,就可以通过配置不同的底层通信信道来达到不同通信模式需求。
在客户端,我们可以有2种方法来完成对于服务器端契约的使用:
1. 直接引用服务器端契约文件(dll文件)
2. 使用内部专门编写的VS插件(VS Add-in)来完成对于远程服务端契约的动态生成
当然,我们推荐使用第二种方式,因为使用VS插件可以帮助用户自动生成针对于每一个契约操作的异步版本。
目前此内置框架被初步定义在命名空间:KJFramework.ServiceModel内,拥有独立Assembly.
目前此框架中,仍保留了内部扩展能力,日后扩展后,将会通过配置文件来增强配置能力。
适用场景
作为一个轻量级的SOA解决方案,对于此框架,我们建议的场景是内部的SERVER间服务调用。首先,对于外部公有式的解决方案,微软已经提出了WCF。
当然,还有一个老古董级别的WebService. 所以,对于部门间的服务调用,WCF和WebService可以作为整合不同技术平台的一个公有接口。
我在SERVER SIDE的开发已经有很多年了,据我了解和熟知,几乎大多数的SERVER SIDE开发,内部的服务通讯都会有属于自己的一套框架,内部的服务调用
,如果采用SOA方式的话,也不会选用WCF,因为它确实太重了,而REMOTING服务存在内部的BUG,这个BUG是只有固定场景才会出现的,更何况这些解决方案的可驾驭型不强。 一个私有的SOA解决方案,对于内部服务的调用是具有积极效应的。于是,我自己设计
并完成了这套轻量级的SOA解决方案, 目前这套框架支持2种通讯方式,TCP, IPC(命名管道),日后会支持更多的通讯方式。
内部设计&使用体验
这套框架,我遵从了类似于WCF技术的使用体验,因为这种设计体验会使得使用者以更少的成本去用它。首先,我们来举个例子,看一下服务器端对于服务契约的声明:
//以下为服务端契约接口和实现 [ServiceContract(ServiceConcurrentType = ServiceConcurrentTypes.Concurrent, Description="这是一个契约的描述", Name = "IHellowWorld 服务契约", Version = "0.0.0.1")] public interface IHelloWorld { [Operation(IsOneway = true)] void Increment(); [Operation] int[] Hello(int[] text); [Operation] String Hello(int x, int y, string z); [Operation] string TestMultiPackage(); [Operation] string TestIntellectObject(InObject inObject); [Operation] InObject3 TestReturnValue(string content); } //服务器端实现,具体方法的实现就不写了 :) public class HelloWorld : IHelloWorld { /*Some code here.*/ }
从中,我们可以看到,每一个服务器端契约的操作,我们都为它打上了一个 OperationAttribute, 这个标签是至关重要的,它直接的标示了一个契约中的某些可用操作。
当然,一个OperationAttribute内部拥有很多属性,我们来介绍一下:
好吧,我们再来看一下客户端拿到的服务器端契约接口会是个什么样子呢?
[ServiceContract(ServiceConcurrentType = ServiceConcurrentTypes.Singleton, Description="这是一个契约的描述", Name = "IHellowWorld 服务契约", Version = "0.0.0.1")] public interface IHelloWorld { [Operation(MethodToken = 10655301)] void Increment(); [Operation(MethodToken = 100663310)] string TestMultiPackage(); [Operation(MethodToken = 100663311)] string TestIntellectObject(InObject inObject); //可以看到,此方法是异步的,异步方法不会由服务器端声明,而是由客户端通过我们的插件生成的 [Operation(IsAsync = true, MethodToken = 100663308)] void TestIntellectObjectAsync(InObject inObject, AsyncMethodCallback callback); }
来请注意,这里的AsyncMethodCallback是框架内部定义的,通常情况下,一个带参数的同步操作,在使用插件生成异步操作时,我们都会为其参数的末尾加入一个 AsyncMethodCallback。那么,我们接下来,看一下,服务器端的程序是如何发布一个属于自己的服务呢? 代码如下:
* *服务端用来公开一个服务的代码 *从此代码可知,公开服务的通讯方式为TCP *地址为本地的9999端口 *公开的服务全地址为: tcp://localhost:9999/Test */ ServiceHost serviceHost = new ServiceHost(typeof(HellowWorld), new TcpBinding("tcp://localhost:9999/Test")); //如果设置此属性,则会让客户端从网页上直接预览此服务的契约 serviceHost.IsSupportExchange = true; serviceHost.Opened += (sender, e) => { Console.WriteLine("Service has been opened."); }; serviceHost.Open();
再来看一下客户端调用服务器端指定契约操作的代码:
//IClientProxy<T>接口是一个动态的客户端代理接口 IClientProxy<IHelloWorld> proxy= new DefaultClientProxy<IHelloWorld>(new TcpBinding("tcp://localhost:9999/Test")); InObject inObject = new InObject { Inf1 = 1, Info2 = DateTime.Now, Info = "Info", Info3 = new[] { "1", "2" }, Info4 = new[] { 3, 4 } }; //调用的使用使用proxy.Channel, 此对象的类型是服务器端公开的契约接口 //从这里看出,客户端调用的这个方法,是个异步版本,是VS插件自动生成的 proxy.Channel.TestIntellectObjectAsync(inObject, delegate(IAsyncCallResult callResult) { if (callResult.IsSuccess) Interlocked.Increment(ref callbackcount); else if (!callResult.IsSuccess && callResult.LastError != null) throw callResult.LastError; });
可以看出什么吗? 哦!是的。 从客户端的调用代码来看,使用起来是多么的简单! 由于客户端使用的是一个服务器端契约的接口,所以对于方法的名称,
方法传入参数的数量,以及参数的强类型,编译器都会默认的进行支持和智能提示。 我们的框架,将会很智能的为服务器端契约接口中的每一个操作来编写动态的MSIL语言,
这样一来,我们的客户端就能直接使用服务器端的接口来进行调用了。
如果,在调用期间,产生了任何*业务逻辑*上的错误,那么客户端是能够知晓的。 我们框架能够支持远程的异常回传,这样的话,当我们调用了一个契约操作,
但是却出错的时候,会有如下2种场景:
什么样子才算是好的使用体验?
为了让使用者,更好的使用我们的框架,我们特地为此框架设计了一个VS的插件,该插件的目的就是为了能够通过远端服务所发布的元数据,来自动的为客户端
生成远端服务的接口。 这个插件的界面看起来是这样的:
当然,此插件现在已经能够正常使用了,我会在日后慢慢的进行优化。
发布元数据
对于每一个RPC服务来说,我们框架内部都为其提供了开放元数据功能的支持。一个已开放的RPC服务,如果也开放了元数据,那么意味着,我们就可以使用
刚才提到的插件来生成这个远程服务的本地契约接口啦(当然,也包括传递的第三方类参数等等)。 让一个RPC服务开放其自身的元数据,是很简单的一件事情,我们仅仅需要设置一个属性即可。 就如下:
ServiceHost.IsSupportExchange = true;
如果一个RPC服务开放了元数据,那么我们就可以直接使用本地的浏览器进行远端RPC服务元数据的浏览啦。 效果如下:
服务器端的运行截图如下:
我们也可以查看每一个契约操作的具体细节:
页面的具体元素还没有做更多的细节处理,很多页面的Style都是照扒的,请放心,日后我会进行改进的 :)
服务的调用形式
目前只是完成了第一和第三个,第二个方式的调用正在开发中。
性能测试
我们已经为此框架做了初步的性能测试 这个测试的结果是: callback func 13K/s,也就是说,1万3千个请求 + 1万个3千个请求响应消息 = 1秒
当然,对于框架内部,我们也加入了很多的性能计数器,这样的话,可以以一种很直观的方式来查看当前服务的调用性能。 在 13K/s的性能压力上,
我们并没有看到内存有任何的波动,整体资源也使用正常,线程数量很稳定。 由于测试环境硬件的因素,所以这个结果,仅仅是取决于当时测试的环境而给出的,
这个值也会根据各位不同的测试环境而重新待定。
测试平台:
CPU: Intel(R)Xeon(R)CPU X5670 @2.93GHz @2.93GHz (2处理器)
System: Windows Server 2008 R2 Enterprise
以下是测试代码,各位也能从源代码的ClientTest项目中得到该段代码:
public void Test1() { LightTimer.NewTimer(1000, -1).Start(delegate { Console.Title = String.Format("Callbacks {0}", callbackcount); }, null); Console.WriteLine("开始执行......") IClientProxy<IHelloWorld> instance0 = new DefaultClientProxy<IHelloWorld>(new TcpBinding("tcp://localhost:9999/Test")); InObject inObject = new InObject { Inf1 = 1, Info2 = DateTime.Now, Info = "Info", Info3 = new[] { "1", "2" }, Info4 = new[] { 3, 4 } }; CodeTimer.Initialize(); CodeTimer.Time("Client Performane Test: 1000000", 1000000, delegate { instance0.Channel.TestIntellectObjectAsync(inObject, delegate(IAsyncCallResult callResult) { if (callResult.IsSuccess) Interlocked.Increment(ref callbackcount); else if (!callResult.IsSuccess && callResult.LastError != null) throw callResult.LastError; }); }); Console.ReadLine(); }
附:
此框架目前已经能够完成基本需求了,但是距离商用级别还有待时日。我开源此项目也并不是为了能够将此项目商用,只是希望这个项目能够越来越好,
如果,看了上述的描述,您对此项目很感兴趣,不妨下载下来源代码研究下,又或者是来加入我们的这个项目,一起改进。
在日后,我会为此框架提供越来越多的能力,当然,性能上面也会越来越好。很多的新功能,我也正在策划中,争取能以最快的速度完成。
很感谢各位耐心的看完这篇很长很长的文章。
项目地址: http://servicemodel.codeplex.com/
目前此项目并没有发布二进制的文件,因为我感觉此框架还没有达到初步商用的级别。 但是,仍然可以下载源代码供各位研究。
如果在尝试此框架的途中,有任何问题,您也可以选择与我联系,我将提供最优的技术支持。
QQ:250623008
Email: [email protected]
谢谢.