简单的.NET远程共享动态对象类库:Agency

从本文标题就可以看出,此文(此类库)与之前同样是我搞的VinjEx是同类的。

GitHub: https://github.com/UlyssesWu/Agency

Gitee: https://gitee.com/Ulysses/Agency

在进程间通信时,有时我们想从一个进程(Client)控制另一个进程(Server)中的一个对象。

 

通常情况下,此时可以采用.NET Remoting(或者WCF)创建一个进程间共享对象,这个对象实际存在于Server中,但在Client中生成一个(表面上看类型相同的)代理对象,在客户端对这个代理对象的各种调用将被序列化后发送到Server中运行,然后将结果序列化后传回Client。

不过,这个过程需要Server和Client都“了解”这个对象的类型(即双方都引用了这个类型所在的程序集,通常情况下需要使此类继承接口,而接口定义在一个公共类库中供双方引用)。如果后续开发过程中,需要在这个类/接口里添加新的功能,那么也就需要Server和Client两边同步更新。这就显得相当不灵活;另外,为了在进程间共享,这个类型必须继承自MarshalByRefObject,有时这也会造成不便(毕竟C井不允许多继承)。

Agency就是为了解决上述问题而编写的库。使用Agency,Server可将任意对象作为共享对象(无需继承自MarshalByRefObject也无需继承接口),Client可生成dynamic类型(而非具体类型)的代理对象,对此dynamic对象的各类调用将转换为可序列化的指令(RemoteInvocation)发送到Server,在真正的对象上对此指令进行重放(ReplayOn)以完成调用过程。

使用dynamic对象所带来的灵活性是显而易见的:Server无需再将共享对象的类/接口单独拿出来做一个公共类库,甚至可以将完全不同类型的对象进行共享(还不用继承MarshalByRefObject),只要能让Client通过dynamic调用到它想调用的(方法、属性、事件)就行了。Client也无需再引用别的库,不用知道类型就可以开始xjb写,即使以后Server在共享的类型中加了什么方法,Client也不用重新编译。

举个例子:如果Client的行为是每隔一段时间调用一次 agent.WriteLine("Forty-Seven"); 那么Server甚至可以将真正的对象在TextWriterSerialPort之间来回切换,即使两者没有共同接口——因为两者都有WriteLine(string)方法。

除了灵活之外,Agency的另一个特色是订阅事件(为了与Agency相配而称作Contract)。在Client生成的dynamic对象中,你可以订阅Server上的真实对象的事件,而且有两种模式:

一种是使用Func<T>,即标准的delegate,把Client中的方法作为事件处理程序。这样订阅的事件被触发后,会通知到Client,方法在Client中执行。

另一种是使用Expression<T>,即表达式树,把lambda表达式发送到Server编译后作为事件处理程序。这样订阅的事件直接在Server中执行。不过需注意Expression<T>的表达能力非常有限,不能支持code block(简单地说,不能写大括号)。

如果你还没有意识到这两种方式的区别,请看下例:

Action<string> action = s => Console.WriteLine(s);
agent.OnTriggered += action; //字符串会输出在Client端(agent所在的一端)

Expression<Action<string>> exp = s => Console.WriteLine(s);
agent.OnTriggered += exp; //字符串会输出在Server端(真正的对象所在的一端)

Agency的局限:

效率问题是不必多说的,本身dynamic就会在一定程度上降低效率;另外在订阅事件时,会产生动态类型和编译动态方法,时间消耗会较为明显(但是事件触发调用不会有太大消耗)。

另一个明显的局限是来回传参必须是可序列化(Serializable)或者继承自MarshalByRefObject的类型。这也是理所当然的。通常情况下只用基本类型不会有任何问题。

Demo

下图是使用Agency在Unity(mono)程序和.NET Console(CLR)程序中共享了Unity程序中的一个对象(其类型对Console程序是未知的,Console并没有引用Unity的程序集),从而以非常舒适的语法(在Console程序中订阅Unity程序中的事件、调用Unity程序中的方法)实现了一个双向聊天功能。

评论 (5) -

  • 谢谢U大,
    早前拜读了 VinjEx,Ipc 觉得很方便简易,
    最近有一个需求是多个Server 在一个Channel 上自动成为主副,并且主发生问题时由副成主继续运作,不知道U大可以提供一些思路吗?
    • 理清了一下问题,当Server发生问题(突发性),这个Channel之间的Client如何可以继续运作?而且其中一个可以担任Server的工作.
      • 感谢来访。这个问题我并没有了解过,目前只能给你一些我粗浅的想法:
        1. 做一个不会出问题的中心Server,它不提供其它服务,只负责监视其它Server和告知Client当前可用的服务Server。由于不提供服务,它的稳定性应该会比较高吧。
        2. 使你的Server的地址具有规律,这也是目前网站服务常用的手段。若是IPC或是HTTP,可采用递增的地址(如:http://s1.sever.comhttp://s2.sever.com。。。等),若是TCP或UDP可采用递增的端口号。当Client的调用出现异常时,自动向后续的地址请求服务。而新服务端上线的时候,可以模仿Client的方式找出一个空闲地址进行注册。
        • 递增地址方式可以解决
          非常感谢U大的帮助
      • 另外,这个问题WCF应该已经有实现了。你可以以WCF Service Discovery(或“发现代理”)为关键字进行搜索。如果你并不是必须用.NET Remoting的话,可以考虑用WCF。

添加评论

Loading