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)

 

using System;

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 System;
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;
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):

<?xml version="1.0" encoding="utf-8" ?>
<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 資訊:

 

<?xml version="1.0" encoding="utf-8" ?>
<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>
<channel ref="tcp" port="7777" > :等同於利用 TcpChannel 註冊一個 port 7777 的通道。<wellknown type="RemotingLib.RemotingMsg, RemotingLib" mode="Singleton" objectUri="RemotingTest"/> :等同於透過 RemotingConfiguration.RegisterWellKnownServiceType 進行註冊。
這邊常有人搞錯 type="RemotingLib.RemotingMsg, RemotingLib" 的設定方式。簡單來說,逗號(,)前是有繼承 MarshalByRefObject 的類別名稱(含命名空間),逗號(,)後是組件的名稱。
所以,我們在這邊填入的是 RemotingLib.RemotingMsg 這個類別,因為它繼承了 MarshalByRefObject ;至於組件名稱就填入 RemotingLib 。
如果你將類別寫在 RemotingServer 專案中,因為建置後的這個類別會在 RemotingServer.exe 中,所以組件名稱就要填入 RemotingServer 。(基本上這個組件名稱沒說一定是 dll 的名稱)接下來修改一下 RemotingServer 專案中的 Program.cs 檔:
using System;
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 物件都是使用無參數的建構子進行初始化的動作。
也許有人會問:

我可以讓 RemotingMsg 這個類別的另一個建構子(有參數的),做為 Remoting 預設的服務物件嗎?我想要在 RemotingServer 端啟動的時候,這個 Remoting 物件就跟著啟動,可以嗎?

接下來的步驟,就可以完成上述的需求。

修改 RemotingServer 的 App.config 檔:將 <service> 標籤 mark 掉。(要刪掉也行啦)
<?xml version="1.0" encoding="utf-8" ?>
<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>
修改 RemotingServer 專案的 Program.cs 的程式:(注意:一定要先使用 RemotingConfiguration.Configure ,再使用 RemotingServices.Marshal )
namespace RemotingServer
{
    
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.exe 和 RemotingClient.exe ,應該可以得到以下的畫面:

[檢視圖片]
圖六 RemotingServer 啟動畫面2 

[檢視圖片]
圖七 RemotingClient 啟動畫面2

.Net Remoting 還有一種用法是: Client 可以向 Server 註冊或反註冊事件,當 Server 有事件時,會通知有註冊事件的 Client 。
礙於內容太多,此作法留在下一篇。

posted @ 2009-10-18 16:42  飘渺峰  阅读(339)  评论(0编辑  收藏  举报