阅读内容 

闲时论道:构造基于接口的分布式应用(.NET Framework 2.0)

[日期:2008-08-20] 来源:  作者: [字体: ]
     资历尚浅,还不会用专业术语;有词不达意的地方,朋友们包涵!
  
   我在做一个分布式应用的时候,突想起早前看到的一篇文章,大意是,在分布式客户端与服务端间构建接口,在服务端负责实现,客户端负责调用。优点是,任何“实现”的更改、升级只在服务端更新、再部署即可,而不用牵涉到客户端。对于系统维护,这是大有裨益的。
  
   我想是自己长大了,从前对这个思路似懂非懂,如今通过搜索原始素材、“使劲”的解读模仿(相当的用力),应该说基本理解了。
  
   这个思路的要点在于:接口库同时部署在服务端、客户端,实现接口的运行库部署在服务端,但在客户端需要部署一个欺骗编译器的“假运行库”;“假运行库”旨在使得服务端的实现类在客户端得到明调用。举例如下:
  
  
  --------------------------------------------------------------------------------
  
   这是服务端、客户端都有部署的接口函数(RemotingInterface.dll):
  
  using System;
  
  namespace RemotingInterface {
   /// <summary>
   /// 分布式管理者. 这个类存在的意义在于: 其帮助客户端通过公用接口,
   /// 而在服务器端创建接口相对应的类型. 该类作为接口与类型的构造器出现.
   /// </summary>
   public interface IRemotingManager {
  
   /// <summary>
   /// 构造公用接口的服务器端类型实例.
   /// </summary>
   /// <typeparam name="I">公用接口.</typeparam>
   /// <param name="erroMessage">意外信息.</param>
   /// <returns>公用接口对应类型的实例.</returns>
   I GetObject<I>(out string erroMessage) where I: class;
  
   /// <summary>
   /// 服务器地址. 由客户端初始化服务端实例的服务器地址.
   /// </summary>
   /// <param name="formatUrl">服务器地址.</param>
   /// <param name="erroMessage">意外信息.</param>
   void InitServerUrl(string formatUrl, out string erroMessage);
  
   }
  }
  
  --------------------------------------------------------------------------------
  
   这是服务端“真实现”的类(RemotingLibrary.dll):
  
  using System;
  using System.Runtime.Remoting;
  using RemotingInterface;
  using System.Collections;
  
  namespace RemotingLibrary {
  
   /// <summary>
   /// 服务端分布式管理者. 实际继承分布式管理接口.
   /// </summary>
   public class RemotingManager: MarshalByRefObject, IRemotingManager {
  
   #region Server Instance
  
   private static Hashtable constructors = new Hashtable();
  
   static RemotingManager() {
   RemotingConfiguration.Configure(AppDomain.CurrentDomain.FriendlyName + ".config", false);
   }
  
   /// <summary>
   /// 服务端注册服务类型.
   /// </summary>
   /// <typeparam name="I">服务接口名称.</typeparam>
   /// <typeparam name="T">服务类型名称.</typeparam>
   public static void RegistService<I, T>()
   where I: class
   where T: MarshalByRefObject, I, new() {
   string name = typeof(I).Name;
   if(!constructors.ContainsKey(name))
   constructors.Add(name, new Constructor<T>());
   }
  
   /// <summary>
   /// 服务端卸载服务类型.
   /// </summary>
   /// <typeparam name="I">服务接口名称.</typeparam>
   public static void UnRegistService<I>() where I: class {
   string name = typeof(I).Name;
   if(constructors.ContainsKey(name))
   constructors.Remove(name);
   }
  
   #endregion
  
   private string serverUrl = null;
   private bool serverUrlInit = true;
  
   #region IRemotingManager 成员
  
   T IRemotingManager.GetObject<T>(out string erroMessage) {
   try {
   erroMessage = null;
   IConstructor cstr = constructors[typeof(T).Name] as IConstructor;
   if(cstr == null) {
   erroMessage = string.Format("服务端未注册的接口类型 {0}", typeof(T).Name);
   return null;
   }
   if(serverUrl == null)
   return cstr.GetObject() as T;
   else
   return cstr.GetObjectByUrl(serverUrl) as T;
   }
   catch(Exception E) {
   erroMessage = E.Message;
   }
   return null;
   }
  
   void IRemotingManager.InitServerUrl(string formatUrl, out string erroMessage) {
   if(serverUrlInit) {
   serverUrl = formatUrl;
   serverUrlInit = false;
   erroMessage = null;
   }
   else
   erroMessage = "服务器地址已经初始化.";
   }
  
   #endregion
  
   #region Remoting Constructor
  
   interface IConstructor {
   object GetObject();
   object GetObjectByUrl(string formatServerUrl);
   }
  
   class Constructor<T>: IConstructor
   where T: class, new() {
  
   #region IConstructor 成员
  
   object IConstructor.GetObject() {
   return new T();
   }
  
   object IConstructor.GetObjectByUrl(string formatServerUrl) {
   if(string.IsNullOrEmpty(formatServerUrl))
   throw new ArgumentNullException("formatServerUrl", "无法创建指定分布式类型, 远程服务器地址为空.");
   return (T)Activator.GetObject(typeof(T), string.Format(formatServerUrl, typeof(T).Name));
   }
  
   #endregion
   }
  
   #endregion
   }
  }
  
  --------------------------------------------------------------------------------
  
   这是客户端“假实现”的类(RemotingCheater.dll):
  
  using System;
  using System.Runtime.Remoting;
  using System.Threading;
  using RemotingInterface;
  
  namespace RemotingLibrary {
  
   /// <summary>
   /// 客户端分布式管理者. 伪装继承分布式管理接口.
   /// </summary>
   public class RemotingManager: MarshalByRefObject, IRemotingManager {
  
   #region Client Instance
  
   /// <summary>
   /// 服务器地址.
   /// </summary>
   private static readonly string serverManagerUrl = null;
  
   /// <summary>
   /// 读取客户端配置
   /// </summary>
   static RemotingManager() {
   string url = System.Configuration.ConfigurationManager.AppSettings["RemotingServerUrl"];
   if(string.IsNullOrEmpty(url)) {
   try {// WebForm
   RemotingConfiguration.Configure(AppDomain.CurrentDomain.BaseDirectory + "\\web.config", false);
   return;
   }
   catch {
   }
   try {// WinForm
   RemotingConfiguration.Configure(AppDomain.CurrentDomain.FriendlyName + ".config", false);
   return;
   }
   catch {
   }
   // WindowsService
   RemotingConfiguration.Configure(Thread.GetDomain().BaseDirectory + AppDomain.CurrentDomain.FriendlyName + ".config", false);
   }
   else {
   url = url.Replace('\\', '/');
   if(url.EndsWith("/")) {
   url = url + "{0}";
   }
   else {
   url = url + "/{0}";
   }
   serverManagerUrl = url;
   }
   }
  
   /// <summary>
   /// 获取分布式类管理者.
   /// </summary>
   /// <returns></returns>
   public static IRemotingManager GetManager() {
   if(serverManagerUrl == null) {
   return new RemotingManager();
   }
   else {
   RemotingManager rm = (RemotingManager)Activator.GetObject(typeof(RemotingManager),
   string.Format(serverManagerUrl, typeof(RemotingManager).Name));
   string err;
   ((IRemotingManager)rm).InitServerUrl(serverManagerUrl, out err);
   return rm;
   }
   }
  
   #endregion
  
   #region IRemotingManager 成员
  
   T IRemotingManager.GetObject<T>(out string erroMessage) {
   throw new NotImplementedException("只可通过远程获取!");
   }
  
   void IRemotingManager.InitServerUrl(string formatUrl, out string erroMessage) {
   throw new NotImplementedException("只可通过远程获取!");
   }
  
   #endregion
   }
  }
  
   注意:“假实现”与“真实现”的非静态字段及其可访问性必须保持一致,否则运行时将报错;类静态的属性、方法等是各自独立的,并在各自的端点生效:“真实现”的静态属性、方法在服务端生效,“假实现”的静态属性、方法在客户端生效。
  
  
  
  --------------------------------------------------------------------------------
   朋友们看到了,服务端对接口的实现相当复杂,而在客户端只是简单的“假实现”;不用担心,当你在客户端通过构造器 public static IRemotingManager GetManager() { ... } 获取实例的时候,其实是在服务端创建并返回。
  
   不过,这样“真假实现”其实是不优雅的。我尝试在客户端丢弃“假实现”,而通过“接口”直接调用服务端的实现。迂回于泛型的运用,看上去我做到了。上面朋友们看到的 IRemotingManager 就是我用来协助客户端创建“远程接口实例”的辅助类。在部署了上面传统实现 IRemotingManager 的前提下,看下面较优雅的对 IRemotingFile 的应用:
  
  
  --------------------------------------------------------------------------------
  
   两端部署的接口约定(RemotingInterface.dll):
  
  using System;
  using System.Collections.Generic;
  using System.Text;
  
  namespace RemotingInterface {
   /// <summary>
   /// 分布式文件操作
   /// </summary>
   public interface IRemotingFile {
   /// <summary>
   /// 获取指定目录下所有文件列表.
   /// </summary>
   /// <param name="path">指定目录.</param>
   /// <returns>文件列表.</returns>
   string[] GetFileNameList(string path, out string erroMessage);
  
   /// <summary>
   /// 重命名文件.
   /// </summary>
   /// <param name="newFileName">新文件名.</param>
   /// <param name="oriFileName">旧文件名.</param>
   void ChangeFileName(string newFileName, string oriFileName, out string erroMessage);
  
   /// <summary>
   /// 删除文件.
   /// </summary>
   /// <param name="fileName">指定文件.</param>
   void DeleteFile(string fileName, out string erroMessage);
   }
  }
  
  --------------------------------------------------------------------------------
  
   服务端的实现(RemotingLibrary.dll):
  
  using System;
  using System.IO;
  using RemotingInterface;
  
  namespace RemotingLibrary {
   public class RemotingFile: MarshalByRefObject, IRemotingFile {
  
   #region IRemotingFile 成员
  
   string[] RemotingInterface.IRemotingFile.GetFileNameList(string path, out string erroMessage) {
   try {
   erroMessage = null;
   FileInfo[] fis = new DirectoryInfo(path).GetFiles();
   string[] ret = new string[fis.Length];
   for(int i = 0; i < fis.Length; i++) {
   ret[i] = fis[i].FullName;
   }
   return ret;
   }
   catch(Exception E) {
   erroMessage = E.Message;
   }
   return null;
   }
  
   void RemotingInterface.IRemotingFile.ChangeFileName(string newFileName, string oriFileName, out string erroMessage) {
   try {
   erroMessage = null;
   System.IO.File.Move(oriFileName, newFileName);
   }
   catch(Exception E) {
   erroMessage = E.Message;
   }
   }
  
   void RemotingInterface.IRemotingFile.DeleteFile(string fileName, out string erroMessage) {
   try {
   erroMessage = null;
   System.IO.File.Delete(fileName);
   }
   catch(Exception E) {
   erroMessage = E.Message;
   }
   }
  
   #endregion
  
   }
  }
  
  --------------------------------------------------------------------------------
  
   服务端启动(RemotingConsoleServer.exe):
  
  using System;
  using System.Collections.Generic;
  using System.Text;
  using RemotingInterface;
  using RemotingLibrary;
  
  namespace RemotingConsoleServer {
   class Program {
   static void Main(string[] args) {
   Console.WriteLine("正在开启分布式服务 ...");
   RemotingManager.RegistService<IRemotingFile, RemotingFile>();
   //RemotingManager.RegistService<IRemotingImage, RemotingImage>();
   Console.WriteLine("分布式服务已启动 ...");
   Console.ReadLine();
   }
   }
  }
  
  --------------------------------------------------------------------------------
  
   客户端调用(RemotingClient.exe):
  
  using System;
  using RemotingInterface;
  using RemotingLibrary;
  
  namespace RemotingClient {
   class Program {
   static void Main(string[] args) {
   string err;
   IRemotingFile mrc = RemotingManager.GetManager().GetObject<IRemotingFile>(out err);
   string[] fs = mrc.GetFileNameList("d:/Test", out err);
   if(err == null){
   Console.WriteLine("已获取文件列表:");
   foreach(string s in fs)
   Console.WriteLine("\t" + s);
   }
   else
   Console.WriteLine(string.Format("Exception: {0}", err));
   Console.ReadLine();
   }
   }
  }
  
   朋友们又该注意到了,接口函数中,我无一例外的使用了 out string erroMessage 变量用以传递错误信息。这种方式可能过于粗糙,朋友只作参考,不必延用。有关分布式异常处理方面,webabcd 老大好像有一些处理方案,可惜当初我没有深究。
  
   缺憾:RemotingManager.GetManager() 看来必须每次调用时都如此实时获取,而不能“缓存”(比如,IRemotingManager rm = RemotingManager.GetManager(); IRemotingFile mrc = rm.GetObject<IRemotingFile>(out err);),因为服务端会定时回收已创建的实例,如果缓存,将会不可预知地出现当前实例失效的情况。是否我太懒,我只粗浅的了解了一下服务端所谓的“过时策略”,而没有深入并找出更好的解决方案。“实时获取”大概会使得系统运行“稳定”,但带给性能的负影响我实在无法预估。
  
  
  --------------------------------------------------------------------------------
  
   最后谈较容易被轻视甚至忽略的配置问题。
  
   服务端的配置,很传统,没太多可“计较”的;当增加新的“分布类”时,记得增加它相关的配置:
  
  <?xml version="1.0" encoding="utf-8" ?>
  <configuration>
   <system.runtime.remoting>
   <application name="ServiceProvider">
   <service>
   <wellknown mode="SingleCall" type="RemotingLibrary.RemotingManager,RemotingLibrary" objectUri="RemotingManager"></wellknown>
   <wellknown mode="SingleCall" type="RemotingLibrary.RemotingFile,RemotingLibrary" objectUri="RemotingFile"></wellknown>
   </service>
   <channels>
   <channel ref="tcp server" port="6000"></channel>
   </channels>
   </application>
   </system.runtime.remoting>
  </configuration>
  
   客户端的配置,我的策略是,当 appSetting 中不存在 RemotingServerUrl 配置项,程序将读取 system.runtime.remoting 节;但是,我自己没有测试通过 system.runtime.remoting 配置节获取远程实例,个人万不能保证系统运作正常:
  
  <?xml version="1.0" encoding="utf-8" ?>
  <configuration>
   <appSettings>
   <!--优先读取-->
   <add key="RemotingServerUrl" value="tcp://192.168.*.*:6000"/>
   </appSettings>
   <!--当 appSettings 中 RemotingServer 不存在或为空时, 读取该节 system.runtime.remoting.-->
   <!--<system.runtime.remoting>
   <application>
   <client>
   <wellknown type="RemotingLibrary.RemotingManager,RemotingLibrary" url="tcp://192.168.*.*:6000/RemotingManager"/>
   <wellknown type="RemotingLibrary.RemotingFile,RemotingLibrary" url="tcp://192.168.*.*:6000/RemotingFile"/>
   </client>
   </application>
   </system.runtime.remoting>-->
  </configuration>
  
  --------------------------------------------------------------------------------
  
   最大的遗憾:WCF 等框架已经进入平民视野,我现在把玩的不过是昨日黄花。我刚学会了一百一十米栏,微软告诉我改耍自行车了。料想明天还会改玩赛车、飞机甚至火箭!我们是幸福的“把玩者”呢,还是不幸的“追新人”?
  
    
阅读:
录入:blue1000

推荐 】 【 打印
相关新闻      
本文评论       全部评论
发表评论
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款


点评: 字数
姓名:
Advertisement
内容查询


Advertisement