专栏.NET Remoting 技術漫談(下)

专栏.NET Remoting 技術漫談(下)
  
.NET Remoting 技術漫談 


代理物件(proxy object) 

至於提供服務的遠端物件,它和一般物件的差別在於繼承了MarshalByRefObject類別,提供跨應用程式定義域的存取能力。而當前端要使用遠端物件時,並不是直接和遠端物件進行通訊,而是透過一個代理物件進行通訊的動件。這個代理物件是由 .NET Remoting主動產生,它熟悉 .NET Remoting通訊協定的處理,也隱藏了作業上的細節,讓前端的程式可以在不知覺通訊對象位置的情況下進行遠端服務取用的作業。這個作業的過程可以用下圖來說明: 
 
按此在新窗口浏览图片
圖中的遠端處理系統就是 .NET Remoting,兩個不同應用程式定義域的物件是透過所謂的通道(channel)進行溝通作業,而代理物件則是熟悉如何和遠端處理系統打交道,同時熟知遠端伺服器物件的定義
通道 

通道主要是用來傳遞進出遠端物件的訊息。當一個前端呼叫了遠端物件提供的方法時,這個方法所需要用到的參數、以及其他相關的資訊都是透過通道傳遞給遠端物件。而要回傳的結果也是透過同樣的方式傳回到前端。前端的程式可以選擇任何一個在伺服端已經註冊的通道和遠端物件進行通訊,所以開發人員可以自由選擇最合適的通道進行溝通。我們除了能修改既有的通道安排之外,也有可能擴充建立新的通道,使用不同的通訊協定。基本上通道的選擇是依據以下的規則處理: 

至少要註冊一個通道供遠端物件使用,在物件註冊之前就要先註冊好通道。 
通道是依據應用程式定義域註冊。由於一個執行程序中可能有多個應用程式定義域,所以當某個執行程序消失時,所有這個執行程序中的應用程式定義域註冊的通道都會被銷毀。 
我們不可以將傾聽同一個通訊埠的同一個通道註冊多次。雖然通道是依據應用程式定義域進行註冊,同一台電腦上不同的應用程式定義域並不能同時註冊使用同一個通訊埠的通道。不過您可以註冊同一個通道傾聽不同的通訊埠。 
前端程式可以透過遠端物件註冊的任一個通道進行溝通。 .NET Remoting會確保當前端嘗試透過正確的通道連結到遠端物件時,能夠順利完成作業。前端程式必須自行在和遠端物件溝通之前先呼叫ChannelService類別的RegisterChannel 方法指示取用的通道。當然,同樣的動作也可以透過配置檔完成。 
物件的生命期 

不論是那一種類型的物件,都有生命期的問題。單次呼叫物件的生命期很好決定,就是一次呼叫作業的期間就是它的生命期。至於單一物件和前端啟用物件,生命期的決定是透過租期的機制來決定。 

如果您熟悉DHCP的作業方式,大概就瞭解租期的概念。在DHCP中,運算裝置取得IP位置通常不是永久性的擁有一個IP位址,而是以租用的方式取得,伺服器上多半安排有租期。當租用的IP位址接近租期時,DHCP的前端程式必須提出續租,如果沒有提出續租的要求,伺服器可以在到達租期時將該IP位址出租給其他的運算裝置。 

同樣的道理,當外界取用一個 .NET Remoting的物件時,是透過物件參照來進行服務的取用。對於單次呼叫物件,這個物件參照雖然持續存在,但是每一次呼叫執行完畢之後,參照其實就指向空值,沒有實際可引用的物件。而對於像單一物件或是前端啟用物件而言,這個物件參照會同時面對一份租約,租約會有對應的租期。同樣的,當租期接近到期時,這個物件就會被 .NET Remoting 的機制中斷參照關係。而一旦對這個物件的所有參照都被釋放時,物件就會被放進資源回收的排程之中,在下次進行回收動作時被收回。換句話說,租約控制了物件的生命期。 

雖然系統提供了預設的租期,但是您可以用不同方式來延展這個期間,並且依據不同伺服器物件的情況提供不同的安排,以便伺服物件能維持前端的狀態,順利提供服務。而伺服物件的租期甚至可以延展到無限長,避免 .NET Remoting 在資源回收的週期中將它收回。 

建立 .NET Remoting應用程式 

對於 .NET Remoting的架構和功能有了概括性的瞭解之後,接下來我們就看看如何建立一個 .NET Remoting的應用程式。一個標準的 .NET Remoting應用程式包含了三個部份: 

提供服務的遠端物件 
用以聆聽前端要求的主應用程式(host application) 
提供物件要求的前端程式
建立遠端物件 

為了將重心放在 .NET Remoting的技術上,我建立了一個很簡單的遠端物件,由於是供外界叫用的物件,所以我建立的是一個類別庫專案。類別的名稱就是RemoteObject,它只提供一個方法GetActiveDomain,這個方法會傳回物件所在應用程式定義域的域名,程式如下: Public Class RemoteObject 
  Inherits MarshalByRefObject 
      
  Public Function GetActiveDomain() As String 
    Return AppDomain.CurrentDomain.FriendlyName 
  End Function 
End Class 


如同之前所述,這個類別繼承自MarshalByRefObject,做為和 .NET Remoting的接口。可能我們會有一個疑問,如果是這樣的話,不就代表既有的功能若是要對遠端提供服務,就必須修改程式?其實我們可以建立一個Helper物件,包裝既有物件的功能,同時對遠端提供服務,這個Helper物件就可以繼承自MarshalByRefObject。 

建立主應用程式 

單只有遠端物件,並不能很有效的進行物件的控制。而且一般遠端物件都是以DLL組件的方式呈現,它必須透過一個主應用程式來提供應用程式定義域的執行環境。我們可以自行建立主應用程式,也可以利用既有的環境做為主應用程式─例如IIS或是 .NET元件服務(之前的COM+)等環境。 

要讓前端能順利取用遠端物件的服務,就必須啟動主應用程式。和DCOM不同的是, .NET Remoting並不會主動為你啟動主應用程式,所以要持續的提供前端的服務,你就必須建立能主動啟動的應用程式,除了像IIS或是 .NET元件服務這類作業系統就已經提供的環境之外,另外一種類型的主應用程式就是Windows服務。 

基於示範的理由,在這裡我直接建立一個簡單的主應用程式,這是一個主控台應用程式,只是透過命令模式顯示被叫用的訊息。除此之外,它還有一個很重要的任務,就是讀取配置檔的內容準備通訊環境,這樣才能接收前端的呼叫,並依據配置的內容啟用遠端物件。這個程式的內容如下: Imports System.Runtime.Remoting  
  
  Module ConsoleHost 
   
 
    Sub Main()
Console.WriteLine("準備通訊環境。")
RemotingConfiguration.Configure("SimpleServer.exe.config")

Console.WriteLine("開始接收訊息...")
Console.WriteLine("按下任意鍵結束作業。")

' 等待
Console.ReadLine()
 
    
  End Sub 
    
End Module 


我們可以看到其中叫用到RemotingConfiguration類別的Configure方法,而提供的是配置檔的名稱,格式是執行檔的檔名再加上.config的延伸檔名。這個程式會維持執行的狀態,直到使用者按下任意鍵為止。程式一旦結束,前端就無法順利的取用遠端物件的服務了。 

既然主應用程式的功能是提供遠端物件執行的應用程式定義域環境,自然就要能找得到遠端物件所在的組件,最簡單的方式就是將組件安放在主應用程式執行檔所在的位置。所以在建立好主應用程式之後,同時也將遠端物件的組件檔複製到相同的目錄當中。 

下面則是SimpleServer.exe.config檔案的內容,它主要提供了幾項資訊─遠端物件的類別名稱、描述類別的中繼資料以及註冊使用通道: <?xml version="1.0" encoding="utf-8" ?>
< configuration> 
  <system.runtime.remoting> 
    <application name="SimpleServer"> 
      <service> 
        <activated type="RemoteObjects.RemoteObject, RemoteObjects" /> 
      </service>
<channels> 
        <channel ref="tcp server" port="8080" /> 
      </channels> 
    </application> 
  </system.runtime.remoting> 
< /configuration> 


在上面檔案中,service標註之中有一個activated標註,這代表啟用的遠端物件是採取前端啟用的模式,而type屬性則指定了要啟用的物件完整的類別名稱,以及組件的名稱。另一方面,我採用的通道是TCP協定,另外一個選擇是HTTP通道,這個時候ref屬性只要指定http即可。指定HTTP通道的好處是可以讓服務透過防火牆的環境提供。同一個標註中同時還指定了通訊埠的編號(8080)。前端應用程式安排好後端的環境之後,接下來就是前端應用程式的設計了。這裡我建立了一個Windows Form應用程式,它的畫面如下: 
在這個畫面中,我要顯示前端程式的定義域名稱以及伺服器元件所在的定義域名稱。它的程式碼如下: Imports System.Runtime.Remoting 
Public Class ClientForm 
 
  Inherits System.Windows.Forms.Form

Private Sub ClientForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
 
    ' 安排前端程式
RemotingConfiguration.Configure("SimpleClient.exe.config")

' 顯示目前的領域
txtClientDomain.Text = AppDomain.CurrentDomain.FriendlyName

' 建立遠端物件,並顯示所在領域
Dim objRemote As New RemoteObjects.RemoteObject
txtServerDomain.Text = objRemote.GetActiveDomain
 
  End Sub 
End Class 


在程式中我們可以再度看到透過配置檔完成基本的配置作業,一旦準備好遠端呼叫的環境,接下來取用物件的方式就和一般的作業無異。所以看來關鍵還是在SimpleClient.exe.config這個檔案上面,以下就是檔案的內容: <?xml version="1.0" encoding="utf-8" ?>
< configuration> 
  <system.runtime.remoting> 
    <application name="SimpleClient"> 
      <client url="tcp://localhost:8080/SimpleServer"> 
        <activated type="RemoteObjects.RemoteObject, RemoteObjects" /> 
      </client>
<channels> 
        <channel ref="tcp client"/> 
      </channels> 
    </application> 
  </system.runtime.remoting> 
< /configuration> 


這個配置檔的內容和伺服器上的主應用程式配置檔很像,唯一的差別是更進一步指出了要呼叫對象所在的位置。由於我採用的是TCP通訊協定,所以提供的網址就是tcp://….,因此當我們的伺服物件要遷移到其他的伺服器上或是更改通訊埠時,就可以更改配置檔中的設定,不牽涉到程式碼的修改。 

談到這裡,是不是就可以完成設計作業,進行測試的工作呢?很抱歉,還缺一道手續,就是前面提的代理物件。對於前端而言,到目前為止都還沒有任何遠端物件的資訊,所以前端程式無從得知所呼叫的遠端物件方法正確的定義,自然無法在執行時順利的進行呼叫。而且還有一個設計時會碰到的重要議題,就是若無法事先取得遠端物件的中繼資料,在進行設計時就無法透過IntelliSense的技術取用類別的方法,編譯時更不可能順利進行編譯動作。所以我們必須提供代理程式,它在設計時期的作用有點像之前COM時代的type library,提供的不是完整的程式執行功能,而僅只是和類別有關的定義資訊(也就是中繼資訊)。 

我們可以在編譯時要求只產生提供這樣中繼資訊的定義檔,也可以直接拿已經編譯好的RemoteObjects.dll給前端。因為在 .NET的架構中,組件檔中就提供了完整的中繼資料。雖然直接提供組件檔給前端不是理想的作法,不過就示範的角度,我還是先在前端程式中直接參照到遠端物件所在的組件,達成同樣的效果。因此在配置檔中activated標註中所指定的同樣也有類別的完整名稱和所在組件的名稱。 

如果我們只想建立含有中繼資料的檔案,可以借助 .NET SDK中提供的工具SOAPSUDS.EXE,它可以協助我們依據既有類別產生以SOAP協定描述的資料,它是一個WSDL(Web Service Description Language)檔案。而前端程式可以依據這份檔案的描述建立代理物件。 

一旦完成了設計,我們可以測試一下結果。首先我在伺服端啟動SimpleServer程式,出現如下的畫面:
我們可以看到,伺服元件實際所在的應用程式定義域是SimpleServer.exe,也就是我們的主應用程式。而如果我們關閉了主應用程式,從新執行前端程式,就會看到執行例外的畫面,通知我們無法順利執行應用程式。 

配置檔 

從上面的例子中,可以看到配置檔在整個通訊的作業中佔有重要的地位,同時也是 .NET Remoting之所以能彈性設定的重要原因之一。在配置檔中安排的資訊都可以在程式中進行控制,只是相對的你的程式碼就必須設計的很聰明,否則會增加不斷修改以應付新環境的機率。一般配置檔會提供以下五個資訊: 

主應用程式的資訊 
物件的名稱 
物件的位置 
註冊的通道,我們可以一次註冊數個通道 
伺服器物件的租期資訊
所以前面提到可以修正租約中有關租期設定的部份就是在這裡安排,例如下面是一段指定租期的標註: 
<lifetime leaseTime="20ms" sponsorshipTimeout="20ms"renewOnCallTime="20ms" />


這個標註指定了標準租期是20毫秒,而在呼叫發生時就續租20毫秒。如果有指定贊助者,逾時的時限是20毫秒。 

為了突顯出配置檔的重要性,同時也強調 .NET Remoting中程式化能力的完整情況,我再度以租期處理的例子,看看若是透過程式,大概是如何作業的。 

當前端的程式想要瞭解伺服物件的租期資訊時,可以呼叫RemotingServices.GetLifetimeService方法從應用程式定義域的租約管理者取得伺服物件的租期長度。此外, .NET Framework中提供了租約類別─Lease,一旦取得一個租約物件(Lease),前端程式可以呼叫Lease.Renew方法來延展租期。 

除了由前端程式主動提出續租的要求之外,也可以對應用程式定義域的租約管理者針對特定的租約註冊所謂的贊助者(sponsor)。一旦遠端物件的租約到期,租約管理者會回呼贊助者提出續租的要求。這是我們前面看到sponsorshipTimeout屬性相關的部份。 

另一方面,如果有指定ILease::RenewOnCallTime屬性的值,每一次呼叫遠端物件的動作都會在進行續租時,依據RenewOnCallTime 屬性的值做為展期的期間。這就是我們在前面看到renewOnCallTime屬性的作用了。 

如果你要進行的設計並不複雜,會選擇用程式碼控制,還是用配置檔控制呢? 

結語 

和過去Win32 API或是COM最大的差別,是 .NET從一開始就以身為良好的網路公民為職志,所以也整合了完整的網路支援能力及作業基礎架構。這是 .NET Remoting Framework之所以能提供豐富多樣的作業模式的主因之一。更重要的是,它絕對不是一個和Web Service相抗衡的技術,反而能夠和Web Service充份整合。像我們前面提到建立遠端物件中繼資料的部份,就沿用了SOAP的協定對前端提供。只要我們在設計分散式應用程式時能夠妥善規劃,採取適當的架構,兼顧延展能力和設計的便利性就不會是難事。 

在這一次的文章中,我介紹了 .NET Remoting的作業原理和應用方式,如果要往下深入探討,各種物件模型的設計方式、配置檔的設定細節、可傳遞的遠端物件的設計方式都是很重要的主題。此外, .NET Remoting還提供了事件模型和非同步呼叫的能力,讓我們能以更理想的作業模式提供最佳的執行效能。不過在不牽涉到大量程式碼的原則之下,我們先就此打住。下一次,我想和大家談談另一個主題, .NET的保全議題。 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值