资历尚浅,还不会用专业术语;有词不达意的地方,朋友们包涵!
我在做一个分布式应用的时候,突想起早前看到的一篇文章,大意是,在分布式客户端与服务端间构建接口,在服务端负责实现,客户端负责调用。优点是,任何“实现”的更改、升级只在服务端更新、再部署即可,而不用牵涉到客户端。对于系统维护,这是大有裨益的。
我想是自己长大了,从前对这个思路似懂非懂,如今通过搜索原始素材、“使劲”的解读模仿(相当的用力),应该说基本理解了。
这个思路的要点在于:接口库同时部署在服务端、客户端,实现接口的运行库部署在服务端,但在客户端需要部署一个欺骗编译器的“假运行库”;“假运行库”旨在使得服务端的实现类在客户端得到明调用。举例如下:
--------------------------------------------------------------------------------
这是服务端、客户端都有部署的接口函数(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 等框架已经进入平民视野,我现在把玩的不过是昨日黄花。我刚学会了一百一十米栏,微软告诉我改耍自行车了。料想明天还会改玩赛车、飞机甚至火箭!我们是幸福的“把玩者”呢,还是不幸的“追新人”?

BK网络学院主要内容:平面设计教程,网站开发在线教程,网页制作教程,服务器教程,网络编程,数据库教程等。