天天看點

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

前言:随着對Remoting的逐漸深入學習,覺得Remoting技術真是博大精深,很多内容對于我來說均是全新的知識。自知技術造詣淺陋,唯有以勤補拙。我很希望自己能寫一些有用的文章,一方面記錄自己的足迹,另一方面也能為别人提供某些幫助。然後,我在想,将文章命名為專題系列是否有些大了?也許吧,不過我總認為,目标不妨訂高一些,壓力不妨給自己多一些,也許努力下去,會有水到渠成的時候。

一、遠端對象的激活

在Remoting中有三種激活方式,一般的實作是通過RemotingServices類的靜态方法來完成。工作過程事實上是将該遠端對象注冊到通道中。由于Remoting沒有提供與之對應的Unregister方法來登出遠端對象,是以如果需要注冊/登出指定對象,微軟推薦使用Marshal(一般譯為編組)和Disconnect配對使用。在《Microsoft .Net Remoting系列專題之一:.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;

我們也可以通過配置檔案來設定生命周期,如:

<?xml version="1.0" encoding="utf-8" ?>

<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服務啟動,按<Enter>退出..."); 

Console.ReadLine();

3、用戶端

通過Activator.GetObject()獲得兩個遠端對象,并調用其方法PrintString。代碼略。

4、運作測試:

運作伺服器端和用戶端,由于監控程式将監視遠端對象的編組程序,是以在運作開始,就會顯示遠端對象已經被Marshal:

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

(截圖的時候出了問題,重新啟動了應用程式,是以時間有點差異)

然後再用戶端調用這兩個遠端對象的PrintString方法,伺服器端接受字元串:

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

一分鐘後,遠端對象一自動被Disconnect:

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

此時用戶端如要調用遠端對象一,會抛出RemotingException異常;

又一分鐘後,遠端對象二被Disconnect了:

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

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

五、結論

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

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

繼續閱讀