前言
------------------
本文介绍了一种全新的调用远程代码的技术。参考了微软的remoting、webservice。
基础知识
------------------
先抛开具体的代码,如果要实现远程代码调用,一个最简单的模型是:
1. 使用一个HttpHandler,当有请求的时候,调用对应的代码,返回。例如:
{
public bool IsReusable
{
get
{
return true;
}
}
public void ProcessRequest(HttpContext context)
{
//这里创建实例RemotingGreeting
RemotingGreeting greeting = new RemotingGreeting();
byte[] response = greeting.Helloworld();
//这里返回调用结果到客户端
context.Response.Clear();
context.Response.ContentType = "application/octet-stream";
BinaryWriter writer = new BinaryWriter(context.Response.OutputStream);
writer.Write(response);
writer.Flush();
writer.Close();
context.Response.End();
}
}
2. 客户端使用Http去访问这个Handler,就实现了最原始的远程调用。
这段代码,就实现了远程调用RemotingGreeting这个类,获取方法Helloworld();的返回值。
那么,这个过程如何实现通用呢?如何实现框架化?首先先看看实际代码的调用效果:
代码实例
------------------
首先声明一个被远程调用的对象,RemotingGreeting. 以及一个接口IRemotingGreeing
{
public string Greeting(string message)
{
return "Hi! " + message;
}
}
[Remote("Pixysoft.Framework.Remoting.Demo", "Pixysoft.Framework.Remoting.Demo.RemotingGreeting")]//这里实际指定了接口具体实现的类的Assembly和Type
public interface IRemotingGreeting
{
string Greeting(string message);
}
然后本地实现远程调用:
using System.Collections.Generic;
using System.Text;
namespace Pixysoft.Framework.Remoting.Demo
{
class testcase
{
public void test()
{
//指定了调用的入口点url
string url = "http://localhost:1300/Apis/remoting.asmx";
//创建本地调用的透明代理
IRemoteChannel<IRemotingGreeting> channel = RemotingManager.CreateRemoteChannel<IRemotingGreeting>(url);
//登录远程服务器
channel.Login("xxxxxx", "xxxxxxxxx");
//远程调用
string greeting = channel.RemoteProxy.Greeting("pixysoft");
//登出
channel.Logout();
//打印结果,就是“Hi!pixysoft”
Console.WriteLine(greeting);
}
}
}
正文
------------------
远程调用框架的思路是:
1. 本地创建一个透明代理(RealProxy.GetTransparentProxy())
2. 用户本地的请求,被透明代理序列化为XML
3. XML传递到服务器的Handler,被解析后,加载对应的对象(Spring? 动态加载)
4. Handler运行对象,获取返回值,再序列化为XML,返回本地。
5. 本地透明代理解析XML,获取返回值。
第一步,创建透明代理。请各位先阅读一篇相关的文章:
http://www.cnblogs.com/zc22/archive/2010/02/22/1671557.html
这里贴出核心代码的一个例子:
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
namespace Pixysoft.Framework.TestDrivens
{
public class Mock<TInterface> : RealProxy
{
public Mock()
: base(typeof(TInterface))
{
}
public TInterface Value
{
get
{
return (TInterface)this.GetTransparentProxy();
}
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage methodCall = msg as IMethodCallMessage;
//我返回int = 1
return new ReturnMessage(1, null, 0, null, methodCall);
}
}
public interface IMock
{
int Devide(int a, int b);
}
public class testrealproxy //测试代码在这里!!!
{
public void test()
{
IMock mock = new Mock<IMock>().Value;
Console.WriteLine(mock.Devide(1, 2));
//输出 = 1
}
}
}
这篇文章讲解了如何实现一个接口的透明代理。本质在
这里,对用户调用的方法进行序列化操作。
第二步,调用的序列化。
上文透明代理通过以下代码获取了用户调用的方法反射
MethodInfo method = methodCall.MethodBase as MethodInfo;
这里,要对调用方法MethodInfo进行序列化。当然,就是自己去建立一个MethodInfo的xml描述,例如:
<parameter type="DateTime" parameter="para1">2010-4-12 下午 08:52:21</parameter>
<parameter type="String" parameter="para2">2</parameter>
<parameter type="Int32" parameter="para3">12</parameter>
<parameter type="IRemotingValue" parameter="para4" />
<return type="IRemotingValue" />
</method>
这个是我实际建立的MethodInfo的xml描述。如何建立就不说了吧,很简单,用StringBuilder去拼就行了。
第三步,httpHandler解析XML,加载对象运行结果。
客户端通过HttpPost到服务端,服务端获取了XML之后,只要根据对应的参数加载Assembly,然后获取对象即可。具体涉及到了一些反射的操作:
Type type = assembly.GetType(typename);
MethodInfo method = type.GetMethod(methodname);
获取了MethodInfo之后,只要把参数放入,获取返回值即可。
ConstructorInfo constructorInfo =
type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
null, new Type[] { }, null);
object remoteObject = constructorInfo.Invoke(new object[] { });
//调用这个对象的方法 这里省略了如何获取parameters过程
object returnvalue = method.Invoke(remoteObject, parameters);
第四步 Handler序列化返回值为XML,返回本地。
只要把returnvalue序列化为xml即可。具体就不叙述了。
第五步 本地透明代理解析XML,获取返回值。
本地透明代理把序列化的returnvalue再反序列化为对象即可,然后返回
难点讲解
------------------
1. 整个调用过程最难的地方在于序列化操作。因为微软不支持接口的序列化、不支持内部类的序列化。这里需要自己实现。
2. 其次最难的在于值类型的操作。因为值类型进入了RealProxy之后,全部被装箱成为了对象(object)。这个时候直接把对象返回会抛异常,因此需要根据具体的method.ReturnType, 逐一用值类型解析返回。
3. 再次,就是动态加载问题。Assembly.LoadFrom会有很多问题,比如版本问题、路径问题。因此要实现一个事件
实现了这个event之后,能够代码指定搜索assembly的位置。具体代码我就不列举了。
后记
------------------
写代码的过程,和拼装模型是一样的。只要我们手上的零件越来越多,能实现的功能和效果就越来越多!