独上高楼网站
  •    你所在位置:首页 VS.netASP.net开发〉闲时论道:构造基于接口的分布式应用(.NET Framework 2.0)
  • 闲时论道:构造基于接口的分布式应用(.NET Framework 2.0)
  • 作者:陛下  文章来源:博客园  发布日期:2008-08-20  浏览次数:186
  • 打印这篇文章
  •   资历尚浅,还不会用专业术语;有词不达意的地方,朋友们包涵!

        我在做一个分布式应用的时候,突想起早前看到的一篇文章,大意是,在分布式客户端与服务端间构建接口,在服务端负责实现,客户端负责调用。优点是,任何“实现”的更改、升级只在服务端更新、再部署即可,而不用牵涉到客户端。对于系统维护,这是大有裨益的。

        我想是自己长大了,从前对这个思路似懂非懂,如今通过搜索原始素材、“使劲”的解读模仿(相当的用力),应该说基本理解了。

        这个思路的要点在于:接口库同时部署在服务端、客户端,实现接口的运行库部署在服务端,但在客户端需要部署一个欺骗编译器的“假运行库”;“假运行库”旨在使得服务端的实现类在客户端得到明调用。举例如下:


        这是服务端、客户端都有部署的接口函数(RemotingInterface.dll):

     
    1. using System;  
    2.  
    3. namespace RemotingInterface {  
    4.     /// 〈summary>  
    5.     /// 分布式管理者. 这个类存在的意义在于: 其帮助客户端通过公用接口,   
    6.     /// 而在服务器端创建接口相对应的类型. 该类作为接口与类型的构造器出现.  
    7.     /// 〈/summary>  
    8.     public interface IRemotingManager {  
    9.       
    10.         /// 〈summary>  
    11.         /// 构造公用接口的服务器端类型实例.  
    12.         /// 〈/summary>  
    13.         /// 〈typeparam name="I">公用接口.〈/typeparam>  
    14.         /// 〈param name="erroMessage">意外信息.〈/param>  
    15.         /// 〈returns>公用接口对应类型的实例.〈/returns>          
    16.         I GetObject〈I>(out string erroMessage) where I: class;  
    17.           
    18.        /// 〈summary>  
    19.        /// 服务器地址. 由客户端初始化服务端实例的服务器地址.  
    20.        /// 〈/summary>  
    21.        /// 〈param name="formatUrl">服务器地址.〈/param>  
    22.        /// 〈param name="erroMessage">意外信息.〈/param>  
    23.         void InitServerUrl(string formatUrl, out string erroMessage);  
    24.           
    25.     }  
    26. }  

        这是服务端“真实现”的类(RemotingLibrary.dll):

     
    1. using System;  
    2. using System.Runtime.Remoting;  
    3. using RemotingInterface;  
    4. using System.Collections;  
    5.  
    6. namespace RemotingLibrary {  
    7.  
    8.     /// 〈summary>  
    9.     /// 服务端分布式管理者. 实际继承分布式管理接口.  
    10.     /// 〈/summary>  
    11.     public class RemotingManager: MarshalByRefObject, IRemotingManager {  
    12.  
    13.         #region Server Instance  
    14.  
    15.         private static Hashtable constructors = new Hashtable();  
    16.  
    17.         static RemotingManager() {  
    18.             RemotingConfiguration.Configure(AppDomain.CurrentDomain.FriendlyName + ".config", false);  
    19.         }  
    20.  
    21.         /// 〈summary>  
    22.         /// 服务端注册服务类型.  
    23.         /// 〈/summary>  
    24.         /// 〈typeparam name="I">服务接口名称.〈/typeparam>  
    25.         /// 〈typeparam name="T">服务类型名称.〈/typeparam>  
    26.         public static void RegistService〈I, T>()  
    27.             where I: class 
    28.             where T: MarshalByRefObject, I, new() {  
    29.             string name = typeof(I).Name;  
    30.             if(!constructors.ContainsKey(name))  
    31.                 constructors.Add(name, new Constructor〈T>());  
    32.         }  
    33.  
    34.         /// 〈summary>  
    35.         /// 服务端卸载服务类型.  
    36.         /// 〈/summary>  
    37.         /// 〈typeparam name="I">服务接口名称.〈/typeparam>  
    38.         public static void UnRegistService〈I>() where I: class {  
    39.             string name = typeof(I).Name;  
    40.             if(constructors.ContainsKey(name))  
    41.                 constructors.Remove(name);  
    42.         }  
    43.  
    44.         #endregion  
    45.  
    46.         private string serverUrl = null;  
    47.         private bool serverUrlInit = true;  
    48.  
    49.         #region IRemotingManager 成员  
    50.  
    51.         T IRemotingManager.GetObject〈T>(out string erroMessage) {  
    52.             try {  
    53.                 erroMessage = null;  
    54.                 IConstructor cstr = constructors[typeof(T).Name] as IConstructor;  
    55.                 if(cstr == null) {  
    56.                     erroMessage = string.Format("服务端未注册的接口类型 {0}", typeof(T).Name);  
    57.                     return null;  
    58.                 }  
    59.                 if(serverUrl == null)  
    60.                     return cstr.GetObject() as T;  
    61.                 else 
    62.                     return cstr.GetObjectByUrl(serverUrl) as T;  
    63.             }  
    64.             catch(Exception E) {  
    65.                 erroMessage = E.Message;  
    66.             }  
    67.             return null;  
    68.         }  
    69.  
    70.         void IRemotingManager.InitServerUrl(string formatUrl, out string erroMessage) {  
    71.             if(serverUrlInit) {  
    72.                 serverUrl = formatUrl;  
    73.                 serverUrlInit = false;  
    74.                 erroMessage = null;  
    75.             }  
    76.             else 
    77.                 erroMessage = "服务器地址已经初始化.";  
    78.         }  
    79.  
    80.         #endregion  
    81.  
    82.         #region Remoting Constructor  
    83.  
    84.         interface IConstructor {  
    85.             object GetObject();  
    86.             object GetObjectByUrl(string formatServerUrl);  
    87.         }  
    88.  
    89.         class Constructor〈T>: IConstructor  
    90.             where T: class, new() {  
    91.  
    92.             #region IConstructor 成员  
    93.  
    94.             object IConstructor.GetObject() {  
    95.                 return new T();  
    96.             }  
    97.  
    98.             object IConstructor.GetObjectByUrl(string formatServerUrl) {  
    99.                 if(string.IsNullOrEmpty(formatServerUrl))  
    100.                     throw new ArgumentNullException("formatServerUrl", "无法创建指定分布式类型, 远程服务器地址为空.");  
    101.                 return (T)Activator.GetObject(typeof(T), string.Format(formatServerUrl, typeof(T).Name));  
    102.             }  
    103.  
    104.             #endregion  
    105.         }  
    106.  
    107.         #endregion  
    108.     }  
    109. }  

        这是客户端“假实现”的类(RemotingCheater.dll):

     
    1. using System;  
    2. using System.Runtime.Remoting;  
    3. using System.Threading;  
    4. using RemotingInterface;  
    5.  
    6. namespace RemotingLibrary {  
    7.  
    8.     /// 〈summary>  
    9.     /// 客户端分布式管理者. 伪装继承分布式管理接口.  
    10.     /// 〈/summary>  
    11.     public class RemotingManager: MarshalByRefObject, IRemotingManager {  
    12.  
    13.         #region Client Instance  
    14.  
    15.         /// 〈summary>  
    16.         /// 服务器地址.  
    17.         /// 〈/summary>  
    18.         private static readonly string serverManagerUrl = null;  
    19.           
    20.         /// 〈summary>  
    21.         /// 读取客户端配置  
    22.         /// 〈/summary>  
    23.         static RemotingManager() {  
    24.             string url = System.Configuration.ConfigurationManager.AppSettings["RemotingServerUrl"];  
    25.             if(string.IsNullOrEmpty(url)) {  
    26.                 try {// WebForm  
    27.                     RemotingConfiguration.Configure(AppDomain.CurrentDomain.BaseDirectory + "\\web.config", false);  
    28.                     return;  
    29.                 }  
    30.                 catch {  
    31.                 }  
    32.                 try {// WinForm  
    33.                     RemotingConfiguration.Configure(AppDomain.CurrentDomain.FriendlyName + ".config", false);  
    34.                     return;  
    35.                 }  
    36.                 catch {  
    37.                 }  
    38.                 // WindowsService  
    39.                 RemotingConfiguration.Configure(Thread.GetDomain().BaseDirectory + AppDomain.CurrentDomain.FriendlyName + ".config", false);  
    40.             }  
    41.             else {  
    42.                 url = url.Replace('\\', '/');  
    43.                 if(url.EndsWith("/")) {  
    44.                     url = url + "{0}";  
    45.                 }  
    46.                 else {  
    47.                     url = url + "/{0}";  
    48.                 }  
    49.                 serverManagerUrl = url;  
    50.             }  
    51.         }  
    52.  
    53.         /// 〈summary>  
    54.         /// 获取分布式类管理者.  
    55.         /// 〈/summary>  
    56.         /// 〈returns>〈/returns>  
    57.         public static IRemotingManager GetManager() {  
    58.             if(serverManagerUrl == null) {  
    59.                 return new RemotingManager();  
    60.             }  
    61.             else {  
    62.                 RemotingManager rm = (RemotingManager)Activator.GetObject(typeof(RemotingManager),  
    63.                  string.Format(serverManagerUrl, typeof(RemotingManager).Name));  
    64.                  string err;  
    65.                 ((IRemotingManager)rm).InitServerUrl(serverManagerUrl, out err);  
    66.                 return rm;  
    67.             }  
    68.         }  
    69.  
    70.         #endregion  
    71.           
    72.         #region IRemotingManager 成员  
    73.  
    74.         T IRemotingManager.GetObject〈T>(out string erroMessage) {  
    75.             throw new NotImplementedException("只可通过远程获取!");  
    76.         }  
    77.          
    78.         void IRemotingManager.InitServerUrl(string formatUrl, out string erroMessage) {  
    79.             throw new NotImplementedException("只可通过远程获取!");  
    80.         }  
    81.  
    82.         #endregion  
    83.     }  
    84. }  

        注意:“假实现”与“真实现”的非静态字段及其可访问性必须保持一致,否则运行时将报错;类静态的属性、方法等是各自独立的,并在各自的端点生效:“真实现”的静态属性、方法在服务端生效,“假实现”的静态属性、方法在客户端生效。

     


           朋友们看到了,服务端对接口的实现相当复杂,而在客户端只是简单的“假实现”;不用担心,当你在客户端通过构造器 public static IRemotingManager GetManager() {  ... }  获取实例的时候,其实是在服务端创建并返回。

     

        不过,这样“真假实现”其实是不优雅的。我尝试在客户端丢弃“假实现”,而通过“接口”直接调用服务端的实现。迂回于泛型的运用,看上去我做到了。上面朋友们看到的 IRemotingManager 就是我用来协助客户端创建“远程接口实例”的辅助类。在部署了上面传统实现 IRemotingManager 的前提下,看下面较优雅的对 IRemotingFile 的应用:


        两端部署的接口约定(RemotingInterface.dll):

     
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4.  
    5. namespace RemotingInterface {  
    6.     /// 〈summary>  
    7.     /// 分布式文件操作  
    8.     /// 〈/summary>  
    9.     public interface IRemotingFile {  
    10.         /// 〈summary>  
    11.         /// 获取指定目录下所有文件列表.  
    12.         /// 〈/summary>  
    13.         /// 〈param name="path">指定目录.〈/param>  
    14.         /// 〈returns>文件列表.〈/returns>  
    15.         string[] GetFileNameList(string path, out string erroMessage);  
    16.           
    17.         /// 〈summary>  
    18.         /// 重命名文件.  
    19.         /// 〈/summary>  
    20.         /// 〈param name="newFileName">新文件名.〈/param>  
    21.         /// 〈param name="oriFileName">旧文件名.〈/param>  
    22.         void ChangeFileName(string newFileName, string oriFileName, out string erroMessage);  
    23.           
    24.         /// 〈summary>  
    25.         /// 删除文件.  
    26.         /// 〈/summary>  
    27.         /// 〈param name="fileName">指定文件.〈/param>  
    28.         void DeleteFile(string fileName, out string erroMessage);  
    29.     }  
    30. }  

        服务端的实现(RemotingLibrary.dll):

     
    1. using System;  
    2. using System.IO;  
    3. using RemotingInterface;  
    4.  
    5. namespace RemotingLibrary {  
    6.     public class RemotingFile: MarshalByRefObject, IRemotingFile {  
    7.       
    8.         #region IRemotingFile 成员  
    9.  
    10.         string[] RemotingInterface.IRemotingFile.GetFileNameList(string path, out string erroMessage) {  
    11.             try {  
    12.                 erroMessage = null;  
    13.                 FileInfo[] fis = new DirectoryInfo(path).GetFiles();  
    14.                 string[] ret = new string[fis.Length];  
    15.                 for(int i = 0; i 〈 fis.Length; i++) {  
    16.                     ret[i] = fis[i].FullName;  
    17.                 }  
    18.                 return ret;  
    19.             }  
    20.             catch(Exception E) {  
    21.                 erroMessage = E.Message;  
    22.             }  
    23.             return null;  
    24.         }  
    25.  
    26.         void RemotingInterface.IRemotingFile.ChangeFileName(string newFileName, string oriFileName, out string erroMessage) {  
    27.             try {  
    28.                 erroMessage = null;  
    29.                 System.IO.File.Move(oriFileName, newFileName);  
    30.             }  
    31.             catch(Exception E) {  
    32.                 erroMessage = E.Message;  
    33.             }  
    34.         }  
    35.  
    36.         void RemotingInterface.IRemotingFile.DeleteFile(string fileName, out string erroMessage) {  
    37.             try {  
    38.                 erroMessage = null;  
    39.                 System.IO.File.Delete(fileName);  
    40.             }  
    41.             catch(Exception E) {  
    42.                 erroMessage = E.Message;  
    43.             }  
    44.         }  
    45.  
    46.         #endregion  
    47.           
    48.     }  
    49. }  

        服务端启动(RemotingConsoleServer.exe):

     
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using RemotingInterface;  
    5. using RemotingLibrary;  
    6.  
    7. namespace RemotingConsoleServer {  
    8.     class Program {  
    9.         static void Main(string[] args) {  
    10.             Console.WriteLine("正在开启分布式服务 ...");  
    11.             RemotingManager.RegistService〈IRemotingFile, RemotingFile>();  
    12.             //RemotingManager.RegistService〈IRemotingImage, RemotingImage>();  
    13.             Console.WriteLine("分布式服务已启动 ...");  
    14.             Console.ReadLine();  
    15.         }  
    16.     }  
    17. }  

        客户端调用(RemotingClient.exe):

     
    1. using System;  
    2. using RemotingInterface;  
    3. using RemotingLibrary;  
    4.  
    5. namespace RemotingClient {  
    6.     class Program {  
    7.         static void Main(string[] args) {  
    8.             string err;  
    9.             IRemotingFile  mrc = RemotingManager.GetManager().GetObject〈IRemotingFile>(out err);  
    10.             string[] fs = mrc.GetFileNameList("d:/Test", out err);  
    11.             if(err == null){  
    12.                 Console.WriteLine("已获取文件列表:");  
    13.                 foreach(string s in fs)  
    14.                     Console.WriteLine("\t" + s);  
    15.             }  
    16.             else 
    17.                 Console.WriteLine(string.Format("Exception: {0}", err));  
    18.             Console.ReadLine();  
    19.         }  
    20.     }  
    21. }  

        朋友们又该注意到了,接口函数中,我无一例外的使用了 out string erroMessage 变量用以传递错误信息。这种方式可能过于粗糙,朋友只作参考,不必延用。有关分布式异常处理方面,webabcd 老大好像有一些处理方案,可惜当初我没有深究。

        缺憾:RemotingManager.GetManager() 看来必须每次调用时都如此实时获取,而不能“缓存”(比如,IRemotingManager rm = RemotingManager.GetManager(); IRemotingFile  mrc = rm.GetObject〈IRemotingFile>(out err);),因为服务端会定时回收已创建的实例,如果缓存,将会不可预知地出现当前实例失效的情况。是否我太懒,我只粗浅的了解了一下服务端所谓的“过时策略”,而没有深入并找出更好的解决方案。“实时获取”大概会使得系统运行“稳定”,但带给性能的负影响我实在无法预估。


        最后谈较容易被轻视甚至忽略的配置问题。

        服务端的配置,很传统,没太多可“计较”的;当增加新的“分布类”时,记得增加它相关的配置

     
    1. 〈?xml version="1.0" encoding="utf-8" ?> 
    2. configuration> 
    3.     system.runtime.remoting> 
    4.         application name="ServiceProvider"> 
    5.             service> 
    6.                 wellknown mode="SingleCall" type="RemotingLibrary.RemotingManager,RemotingLibrary" objectUri="RemotingManager">〈/wellknown> 
    7.                 wellknown mode="SingleCall" type="RemotingLibrary.RemotingFile,RemotingLibrary" objectUri="RemotingFile">〈/wellknown> 
    8.             〈/service> 
    9.             channels> 
    10.                 channel ref="tcp server" port="6000">〈/channel> 
    11.             〈/channels> 
    12.         〈/application> 
    13.     〈/system.runtime.remoting> 
    14. 〈/configuration> 

        客户端的配置,我的策略是,当 appSetting 中不存在 RemotingServerUrl 配置项,程序将读取 system.runtime.remoting 节;但是,我自己没有测试通过 system.runtime.remoting 配置节获取远程实例,个人万不能保证系统运作正常

     
    1. 〈?xml version="1.0" encoding="utf-8" ?> 
    2. configuration> 
    3.     appSettings> 
    4.         〈!--优先读取--> 
    5.         add key="RemotingServerUrl" value="tcp://192.168.*.*:6000"/> 
    6.     〈/appSettings> 
    7.     〈!--当 appSettings 中 RemotingServer 不存在或为空时, 读取该节 system.runtime.remoting.--> 
    8.     〈!--system.runtime.remoting> 
    9.         application> 
    10.             client> 
    11.                 wellknown type="RemotingLibrary.RemotingManager,RemotingLibrary" url="tcp://192.168.*.*:6000/RemotingManager"/> 
    12.                 wellknown type="RemotingLibrary.RemotingFile,RemotingLibrary" url="tcp://192.168.*.*:6000/RemotingFile"/> 
    13.             〈/client> 
    14.         〈/application> 
    15.     〈/system.runtime.remoting>--> 
    16. 〈/configuration> 

        最大的遗憾:WCF 等框架已经进入平民视野,我现在把玩的不过是昨日黄花。我刚学会了一百一十米栏,微软告诉我改耍自行车了。料想明天还会改玩赛车、飞机甚至火箭!我们是幸福的“把玩者”呢,还是不幸的“追新人”?

  • 打印这篇文章
  • 与本文主题相关的文章
  • 返回首页