Remoting 元数据的分离(使用Interface)
一下是转自一位台湾程式设计师的文章.
供大家参考.
首先,也許有人注意到 RemotingLib 這個專案,每次修改都需重新把 RemotingLib.dll 佈署到 Server 與 Client 。
此種設計方式不僅造成將來 Remoting 物件實作部分異動後,Client 佈署上的困難,也間接造成將實作的 dll 曝露在 Client 端的風險。
為了解決上述的問題,我們可以新增一個 InterfaceLib 的類別庫專案,然後讓此專案產生的 InterfaceLib.dll 取代在 Client 端的 RemotingLib.dll ,供 Client 端呼叫。
修改的程序如下: 在 InterfaceLib 專案中新增一個介面程式 IMsg.cs, IMsg.cs 程式碼如下: (注意要用 public interface)
namespace InterfaceLib
{
public interface IMsg
{
DateTime GetServerTime();
string GetServerHelloMsg(string name);
}
}
RemotingLib 專案的修改如下: 將 InterfaceLib 專案加至 RemotingLib 專案的參考。在 RemotingMsg.cs 中,增加 IMsg 的實作。
補充(2009/8/14):因為Remoting物件是有生命週期的(參考初始化租用期),為了不讓這個物件被回收,一般我都會override InitializeLifetimeService()。
程式碼修改如下:(注意增加了 InterfaceLib 的 using ,及 IMsg 的實作)
using InterfaceLib;
namespace RemotingLib
{
public class RemotingMsg : MarshalByRefObject, IMsg
{
//預設的問候語
string strHelloMsg = "Hello~";
/// <summary>
/// RemotingMsg的建構子(無參數)
/// </summary>
public RemotingMsg()
{
Console.WriteLine("RemotingMsg started");
}
/// <summary>
/// RemotingMsg的建構子(參數是用來設定預設的問候語)
/// </summary>
/// <param name="hello"></param>
public RemotingMsg(string hello)
{
this.strHelloMsg = hello;
Console.WriteLine("RemotingMsg started: set msg " + this.strHelloMsg);
}
#region IMsg 成員
/// <summary>
/// 取得Remoting Server的時間
/// </summary>
/// <returns></returns>
public DateTime GetServerTime()
{
return DateTime.Now;
}
/// <summary>
/// 取得Remoting Server的問候訊息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public string GetServerHelloMsg(string name)
{
return string.Format("{0}:{1} {2}", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), this.strHelloMsg, name);
}
#endregion
/// <summary>
/// 讓此物件永不過期
/// </summary>
/// <returns></returns>
public override Object InitializeLifetimeService()
{
return null;
}
}
}
RemotingClient 端的 Program.cs 修改如下: 移除 RemotingLib 的參考,將 InterfaceLib 專案加入參考。呼叫 Remoting 物件後的轉型,改為轉型至 IMsg。
程式碼參考如下
using System.Configuration;
using InterfaceLib;
namespace RemotingClient
{
class Program
{
static void Main(string[] args)
{
try
{
//透過Activator.GetObject取得指定 url 的 Remoting 物件, 並轉換該物件型別至InterfaceLib.IMsg
//url中的IP與port, 要視 RemotingServer 的設定資訊, 進行調整
InterfaceLib.IMsg imsg = (InterfaceLib.IMsg)Activator.GetObject(typeof(InterfaceLib.IMsg), "tcp://127.0.0.1:7777/RemotingTest");
//取得 Server 時間
Console.WriteLine("Get Server Time:");
Console.WriteLine(imsg.GetServerTime());
//取得 Server 的問候訊息
Console.WriteLine("Get Server Message:");
Console.WriteLine(imsg.GetServerHelloMsg(Environment.MachineName));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
string strInput;
Console.WriteLine("請輸入'exit', 以結束程式.");
while (!(strInput = Console.ReadLine()).Equals("exit", StringComparison.OrdinalIgnoreCase)) { }
}
}
}
如此一來,只要 InterfaceLib 中的介面沒異動,RemotingLib 專案中的實作要怎麼改,都跟 Client 參考的組件沒有關係。
最後的專案結構如圖五。(注意:RemotingClient 只有參考 InterfaceLib 專案)
[檢視圖片]
圖五 Remoting 專案結構
不過,如果 Server 端要重新參考新的 RemotingLib.dll,就需要重新啟動 Server 。
但在 TcpChannel 模式下,只要 Server 端重新提供服務, Client 端可在不重新啟動的情況下,使用原本的 Remoting 物件繼續運作。(但要記得 Client 端不要在 try catch 後,把 Remoting 物件給設定成 null)
這也是我比較喜愛使用 TcpChannel 模式的一個原因,IpcChannel 在這部分就很麻煩。第二部分是 RemotingClient 的修改。這個比較簡單,純粹是將 Remoting 的部分搬到設定檔。 在 RemotingClient 專案中,新增一個"應用程式組態檔" 。(此檔案在開發工具中叫作 App.config ,佈署後會叫作 "專案名稱.exe.config" )將 RemotingClient 中呼叫的 url ,設定到組態檔中 (App.config):
<configuration>
<appSettings>
<add key="remotingUri" value="tcp://127.0.0.1:7777/RemotingTest"/>
</appSettings>
</configuration>
修改 Remoting 呼叫的方式如下:
修改 Remoting 呼叫的方式如下://透過Activator.GetObject取得指定 url 的 Remoting 物件, 並轉換該物件型別至InterfaceLib.IMsg //url中的IP與port, 要視 RemotingServer 的設定資訊, 進行調整 InterfaceLib.IMsg imsg = (InterfaceLib.IMsg)Activator.GetObject(typeof(InterfaceLib.IMsg), ConfigurationManager.AppSettings["remotingUri"]);
最後一部分是 Server 的調整。我習慣使用應用程式組態檔做 Remoting 物件的設定。 在 RemotingServer 專案中,新增一個"應用程式組態檔" 。在 App.config 檔案中,建立如下的 Remoting 資訊:
<configuration>
<system.runtime.remoting>
<application name="ServerHost">
<service>
<wellknown type="RemotingLib.RemotingMsg, RemotingLib" mode="Singleton" objectUri="RemotingTest"/>
</service>
<channels>
<channel ref="tcp" port="7777" >
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
這邊常有人搞錯 type="RemotingLib.RemotingMsg, RemotingLib" 的設定方式。簡單來說,逗號(,)前是有繼承 MarshalByRefObject 的類別名稱(含命名空間),逗號(,)後是組件的名稱。
所以,我們在這邊填入的是 RemotingLib.RemotingMsg 這個類別,因為它繼承了 MarshalByRefObject ;至於組件名稱就填入 RemotingLib 。
如果你將類別寫在 RemotingServer 專案中,因為建置後的這個類別會在 RemotingServer.exe 中,所以組件名稱就要填入 RemotingServer 。(基本上這個組件名稱沒說一定是 dll 的名稱)接下來修改一下 RemotingServer 專案中的 Program.cs 檔:
using System.Runtime.Remoting;
namespace RemotingServer
{
class Program
{
static void Main(string[] args)
{
try
{
//透過 RemotingConfiguration.Configure 將 Remoting 的設定讀入
RemotingConfiguration.Configure(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, false);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
string strInput;
Console.WriteLine("請輸入'exit', 以結束程式.");
while (!(strInput = Console.ReadLine()).Equals("exit", StringComparison.OrdinalIgnoreCase)) { }
}
}
}
程式碼看起來簡單多了,只要一行就完成 Remoting 的設定。
最後,不知道有沒有人發現,到目前為止, Remoting 物件都是使用無參數的建構子進行初始化的動作。
也許有人會問:
接下來的步驟,就可以完成上述的需求。
修改 RemotingServer 的 App.config 檔:將 <service> 標籤 mark 掉。(要刪掉也行啦)<configuration>
<system.runtime.remoting>
<application name="ServerHost">
<!--<service>
<wellknown type="RemotingLib.RemotingMsg, RemotingLib" mode="Singleton" objectUri="RemotingTest"/>
</service>-->
<channels>
<channel ref="tcp" port="7777" >
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
{
class Program
{
static void Main(string[] args)
{
try
{
//透過 RemotingConfiguration.Configure 將 Remoting 的設定讀入
RemotingConfiguration.Configure(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, false);
//將問候語改成"Hi~"
RemotingMsg msg = new RemotingMsg("Hi~");
//透過RemotingServices.Marshal轉換此物件
//第二個參數 objectUri ,可參考 RemotingClient 的組態檔, 修改成從設定檔讀入
RemotingServices.Marshal(msg, "RemotingTest", typeof(RemotingMsg));
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
string strInput;
Console.WriteLine("請輸入'exit', 以結束程式.");
while (!(strInput = Console.ReadLine()).Equals("exit", StringComparison.OrdinalIgnoreCase)) { }
}
}
}
[檢視圖片]
圖六 RemotingServer 啟動畫面2
[檢視圖片]
圖七 RemotingClient 啟動畫面2
.Net Remoting 還有一種用法是: Client 可以向 Server 註冊或反註冊事件,當 Server 有事件時,會通知有註冊事件的 Client 。
礙於內容太多,此作法留在下一篇。