Microsoft .Net Remoting系列專題之二:Marshal、Disconnect與生命週期以及跟蹤服務

javaprogramers發表於2006-05-14

Microsoft .Net Remoting系列專題之二 

一、遠端物件的啟用

在Remoting中有三種啟用方式,一般的實現是通過RemotingServices類的靜態方法來完成。工作過程事實上是將該遠端物件註冊到通道中。由於Remoting沒有提供與之對應的Unregister方法來登出遠端物件,所以如果需要註冊/登出指定物件,微軟推薦使用Marshal(一般譯為編組)和Disconnect配對使用。在《Net Remoting基礎篇》中我已經談到:Marshal()方法是將MarshalByRefObject類物件轉化為ObjRef類物件,這個物件是儲存生成代理以與遠端物件通訊所需的所有相關資訊。這樣就可以將該例項序列化以便在應用程式域之間以及通過網路進行傳輸,客戶端就可以呼叫了。而Disconnect()方法則將具體的例項物件從通道中斷開。

根據上述說明,Marshal()方法對遠端物件以引用方式進行編組(Marshal-by-Reference,MBR),並將物件的代理資訊放到通道中。客戶端可以通過Activator.GetObject()來獲取。如果使用者要登出該物件,則通過呼叫Disconnect()方法。那麼這種方式對於編組的遠端物件是否存在生命週期的管理呢?這就是本文所要描述的問題。

二、生命週期

在CLR中,框架提供了GC(垃圾回收器)來管理記憶體中物件的生命週期。同樣的,.Net Remoting使用了一種分散式垃圾回收,基於租用的形式來管理遠端物件的生命週期。

早期的DCOM對於物件生命週期的管理是通過ping和引用計數來確定物件何時應當作為垃圾回收。然而ping引起的網路流量對分散式應用程式的效能是一種痛苦的負擔,它大大地影響了分散式處理的整體效能。.Net Remoting在每個應用程式域中都引入一個租用管理器,為每個伺服器端的SingleTon,或每個客戶端啟用的遠端物件儲存著對租用物件的引用。(說明:對於伺服器端啟用的SingleCall方式,由於它是無狀態的,對於每個啟用的遠端物件,都由CLR的GC來自動回收,因此對於SingleCall模式啟用的遠端物件,不存在生命週期的管理。)

1、租用

租用是個封裝了TimeSpan值的物件,用以管理遠端物件的生存期。在.Net Remoting中提供了定義租用功能的ILease介面。當Remoting通過SingleTon模式或客戶端啟用模式來啟用遠端物件時,租用物件呼叫從System.MarshalByRefObject繼承的InitializeLifetimeService方法,向物件請求租用。

ILease介面定義了有關生命週期的屬性,均為TimeSpan值。如下:
InitialLeaseTime:初始化有效時間,預設值為300秒,如果為0,表示永不過期;
RenewOnCallTime:呼叫遠端物件一個方法時的租用更新時間,預設值為120秒;
SponsorshipTimeout:超時值,通知Sponsor(發起人)租用過期後,Remoting會等待的時間,預設值為120秒;
CurrentLeaseTime:當前租用時間,首次獲得租用時,為InitializeLeaseTime的值。

Remoting的遠端物件因為繼承了MarshalByRefObject,因此預設繼承了InitializeLifetimeService方法,那麼租用的相關屬性為預設值。如果要改變這些設定,可以在遠端物件中重寫該方法。例如:
 public override object InitializeLifetimeService()
 {
  ILease lease = (ILease)base.InitializeLifetimeService();
  if (lease.CurrentState == LeaseState.Initial)
  {
   lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
   lease.RenewOnCallTime = TimeSpan.FromSeconds(20);
  }
  return lease;  
 }

也可以忽略該方法,將物件的租用週期改變為無限:
 public override object InitializeLifetimeService()
 {
  return null;
 }

2、租用管理器

如果是前面所說的租用主要是應用在每個具體的遠端物件上,那麼租用管理器是伺服器端專門用來管理遠端物件生命週期的管理器,它維持著一個System.Hashtable成員,將租用對映為System.DateTime例項表示每個租用何時應過期。Remoting採用輪詢的方式以一定的時間喚醒租用管理器,檢查每個租用是否過期。預設為每10秒鐘喚醒一次。輪詢的間隔可以配置,如將輪詢間隔設定為5分鐘:LifetimeService.LeaseManagerPollTime = System.TimeSpan.FromMinutes(5);

我們還可以在租用管理器中設定遠端物件租用的屬性,如改變遠端物件的初始有效時間為永久有效:
LifetimeServices.LeaseTime = TimeSpan.Zero;

我們也可以通過配置檔案來設定生命週期,如:
<configuration>
 <system.runtime.remoting>
  <application name = "SimpleServer">
   <lifetime leaseTime = "0" sponsorshipTimeOut = "1M" renewOnCallTime = "1M" pollTime = "30S"/>       
  </application>
 </system.runtime.remoting>
</configuration>

注:配置檔案中的pollTime即為上面所說的租用管理器的輪詢間隔時間LeaseManagerPollTime。

租用管理器對於生命週期的設定是針對伺服器上所有的遠端物件。當我們通過配置檔案或租用管理器設定租用的屬性時,所有遠端物件的生命週期都遵循該設定,除非我們對於指定的遠端物件通過重寫InitializeLifetimeService方法,改變了相關配置。也就是說,遠端物件的租用配置優先順序高於伺服器端配置。

3、發起人(Sponsor)

發起人是針對客戶端而言的。遠端物件就是發起人要租用的物件,發起人可以與伺服器端簽訂租約,約定租用時間。一旦到期後,發起人還可以續租,就像現實生活中租方的契約,房東、租房者之間的關係一樣。

在.Net Framework中的System.Runtime.Remoting.Lifetime名稱空間中定義了ClientSponsor類,該類繼承了System.MarshalByRefObject,並實現了ISponsor介面。ClientSponsor類的屬性和方法,可以參考MSDN。

客戶端要使用發起人機制,必須建立ClientSponsor類的一個例項。然後呼叫相關方法如Register()或Renewal()方法來註冊遠端物件或延長生命週期。如:
RemotingObject obj = new RemotingObject();
ClientSponsor sponsor = new ClientSponsor();
sponsor.RenewalTime = TimeSpan.FromMinutes(2);
sponsor.Register(obj);

續租時間也可以在ClientSponsor的建構函式中直接設定,如:
ClientSponsor sponsor = new ClientSponsor(TimeSpan.FromMinutes(2));
sponsor.Register(obj);

我們也可以自己編寫Sponsor來管理髮起人機制,這個類必須繼承ClientSponsor並實現ISponsor介面。

三、跟蹤服務

如前所述,我們要判斷通過Marshal編組遠端物件是否存在生命週期的管理。在Remoting中,可以通過跟蹤服務程式來監視MBR物件的編組程式。

我們可以建立一個簡單的跟蹤處理程式,該程式實現介面ITrackingHandler。介面ITrackingHandler定義了3個方法,MarshalObject、UnmarshalObject和DisconnectedObject。當遠端物件被編組、解組和斷開連線時,就會呼叫相應的方法。下面是該跟蹤處理類的程式碼:public class MyTracking:ITrackingHandler
{
 public MyTracking()
 {
  //
  // TODO: 在此處新增建構函式邏輯
  //
 }

 public void MarshaledObject(object obj,ObjRef or)
 {
  Console.WriteLine();
  Console.WriteLine("物件" + obj.Tostring() + " is marshaled at " + DateTime.Now.ToShortTimeString());
 }

 public void UnmarshaledObject(object obj,ObjRef or)
 {
  Console.WriteLine();
  Console.WriteLine("物件" + obj.Tostring() + " is unmarshaled at " + DateTime.Now.ToShortTimeString());
 }

  public void DisconnectedObject(object obj)
 {
  Console.WriteLine(obj.ToString() + " is disconnected at " + DateTime.Now.ToShortTimeString());
 }
}

然後再伺服器端建立該跟蹤處理類的例項,並註冊跟蹤服務:
TrackingServices.RegisterTrackingHandler(new MyTracking());

四、測試

1、建立兩個遠端物件,並重寫InitializeLifetimeService方法:

物件一:AppService1
初始生命週期:1分鐘

 public class AppService1:MarshalByRefObject
 {
  public void PrintString(string contents)
  {
   Console.WriteLine(contents);   
  }

  public override object InitializeLifetimeService()
  {
   ILease lease = (ILease)base.InitializeLifetimeService();
   if (lease.CurrentState == LeaseState.Initial)
   {
    lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
    lease.RenewOnCallTime = TimeSpan.FromSeconds(20);
   }
   return lease;
   
  }
 }

物件二:AppService2
初始生命週期:3分鐘

 public class AppService2:MarshalByRefObject
 {
  public void PrintString(string contents)
  {
   Console.WriteLine(contents);   
  }

  public override object InitializeLifetimeService()
  {
   ILease lease = (ILease)base.InitializeLifetimeService();
   if (lease.CurrentState == LeaseState.Initial)
   {
    lease.InitialLeaseTime = TimeSpan.FromMinutes(3);
    lease.RenewOnCallTime = TimeSpan.FromSeconds(40);
   }
   return lease;
   
  }
 }

為簡便起見,兩個物件的方法都一樣。

2、伺服器端

(1) 首先建立如上的監控處理類;

(2) 註冊通道:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);

(3) 設定租用管理器的初始租用時間為無限:
LifetimeServices.LeaseTime = TimeSpan.Zero;

(4) 建立該跟蹤處理類的例項,並註冊跟蹤服務:
TrackingServices.RegisterTrackingHandler(new MyTracking());

(5) 編組兩個遠端物件:
ServerAS.AppService1 service1 = new ServerAS1.AppService1();
ObjRef objRef1 = RemotingServices.Marshal((MarshalByRefObject)service1,"AppService1");

ServerAS.AppService2 service2 = new ServerAS1.AppService2();
ObjRef objRef2 = RemotingServices.Marshal((MarshalByRefObject)service2,"AppService2");

(6) 使伺服器端保持執行:
Console.WriteLine("Remoting服務啟動,按退出..."); 
Console.ReadLine();

3、客戶端

通過Activator.GetObject()獲得兩個遠端物件,並呼叫其方法PrintString。程式碼略。

4、執行測試

執行伺服器端和客戶端,由於監控程式將監視遠端物件的編組程式,因此在執行開始,就會顯示遠端物件已經被Marshal:

然後再客戶端呼叫這兩個遠端物件的PrintString方法,伺服器端接受字串:

一分鐘後,遠端物件一自動被Disconnect:

此時客戶端如要呼叫遠端物件一,會丟擲RemotingException異常;

又一分鐘後,遠端物件二被Disconnect了:

align="center">

使用者還可以根據這個程式碼測試RenewOnCallTime的時間是否正確。也即是說,在物件還未被Disconnect時,呼叫物件,則從呼叫物件的這一刻起,其生命週期不再是原來設定的初始有效時間值(InitialLeaseTime),而是租用更新時間值(RenewOnCallTime)。另外,如果這兩個遠端物件沒有重寫InitializeLifetimeService方法,則生命週期應為租用管理器所設定的值,為永久有效(設定為0)。那麼這兩個物件不會被自動Disconnect,除非我們顯式指定關閉它的連線。當然,如果我們顯式關閉連線,跟蹤程式仍然會監視到它的變化,然後顯示出來。

五、結論

通過我們的測試,其實結論已經很明顯了。通過Marshal編組的物件要受到租用的生命週期所控制。注意物件被Disconnect,並不是指這個物件被GC回收,而是指這個物件儲存在通道的相關代理資訊被斷開了,而物件本身仍然在伺服器端存在。

所以我們通過Remoting提供服務,應根據實際情況指定遠端物件的生命週期,如果不指定,則為Remoting預設的設定。要讓所有的遠端物件永久有效,可以通過配置檔案或租用管理器將初始有效時間設為0。

相關文章