跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用


一、引言

   上一篇博文分享了消息隊列(MSMQ)技術來實現分布式應用,在這篇博文繼續分享下.NET平台下另一種分布式技術——.NET Remoting。

二、.NET Remoting 介紹

2.1 .NET Remoting簡介

   .NET REmoting與MSMQ不同,它不支持離線可得,另外只適合.NET平台的程序進行通信。它提供了一種允許對象通過應用程序域與另一個對象進行交互的框架。.NET 應用程序都在一個主應用程序域中執行的,在一個應用程序域中的代碼不能訪問另一個應用程序域的數據,然而在某些情況下,我們需要跨應用程序域,與另外的應用程序域進行通信,這時候就可以采用.NET Remoting技術來實現與另一個程序域中的對象進行交互。

2.2 .NET Remoting基本原理

   .NET Remoting技術是通過通道來實現兩個應用程序之間對象的通信的。首先,客戶端通過Remoting技術,訪問通道來獲得服務器端對象,再通過代理解析為客戶端對象,也稱作透明代理,此時獲得客戶端對象只是服務器對象的一個引用。這既保證了客戶端和服務端有關對象的松散耦合,同時優化了通信的性能。在這個過程中,當客戶端通過透明代理來調用遠程對象的方法時,此時會將調用封裝到一個消息對象中,該消息對象包括遠程對象信息,被調用的方法名和參數,然后透明代理會將調用委托給真實代理(RealProxy對象)的Invoke方法來生成一個IMethodCallMessage,接着通過序列化把這個消息對象序列化成數據流發送到通道,通道會把數據流傳送到服務器端。當服務器接收到經過格式化的數據之后,首先從中通過反序列化來還原消息對象,之后在服務器端來激活遠程對象,並調用對應的方法,而方法的返回結果過程則是按照之前的方法反向重復一遍,具體的實現原理圖如下所示:

2.3 .NET Remoting幾個重要概念

   上面簡單介紹了下.NET Remoting實現分布式應用程序的基本原理,這里介紹下在.NET Remoting中涉及的幾個重要概念。

  1. 遠程對象:是運行在服務器端的對象,客戶端不能直接調用,由於.NET Remoting傳遞的對象是以引用的方式,因此所傳遞的遠程對象必須繼承MarshalByRefObject類,這個類可以使遠程對象在.NET Remoting應用通信中使用,支持對象的跨域邊界訪問。
  2. 遠程對象的激活方式:在訪問服務器端的一個對象實例之前,必須通過一個名為Activation的進程創建它並進行初始化。這種客戶端通過通道來創建遠程對象的方式稱為對象的激活。在.NET Remoting中,遠程對象的激活分為兩大類:服務器端激活和客戶端激活。
  • 服務器端激活,又叫做WellKnow(知名對象)激活模式,為什么稱為知名對象激活模式呢?是因為服務應用程序在激活對象實例之前會在一個眾所周知的統一資源標示符(URI)上發布這個類型,然后該服務器進行會為此類型配置一個WellKnow對象,並根據指定的端口或地址來發布對象。.NET Remoting把服務器端激活又分為SingleTon模式和SingleCall模式兩種。

  SingleTon模式:此為有狀態模式。如果設置為SingleTon激活模式,則.NET Remoting將為所有客戶端建立同一個對象實例。當對象處於活動狀態時,SingleTon實例會處理所有后來的客戶端訪問請求,而不管它們是同一個客戶端,還是其他客戶端。SingleTon實例將在方法調用中一直維護其狀態,類似static成員的概念

  SingleCall模式:是一種無狀態模式。一旦設置為SingleCall模式,則當客戶端調用遠程對象的方法時,Remoting會為每一個客戶端建立一個遠程對象實例,對象實例的銷毀則是由GC自動管理。類似實例成員的概念。

  • 客戶端激活:與Wellknow模式不同,。NET Remoting在激活每個對象實例的時候,會給每個客戶端激活的類型指派一個URI。客戶端激活模式一旦獲得客戶端的請求,將為每一個客戶端都建立一個實例引用。SingleCall模式與客戶端激活模式的區別有:首先,對象實例創建的時間不同。客戶端激活方式是客戶一旦發出調用請求就實例化,而SingleCall則要等到調用對象方法時再創建。其次,SingleCall模式激活的對象是無狀態的,對象聲明周期由GC管理,而客戶端激活的對象是有狀態的,其生命周期可自定義。第三,兩種激活模式在服務器端和客戶端實現的方法不一樣,尤其是在客戶端,SingleCall模式由GetObject()來激活,它調用對象默認的構造函數,而客戶端激活模式,則通過CreateInstance()來激活,它可以傳遞參數,所以可以調用自定義的構造函數來創建實例。

  3. 通道:在.NET Remoting中時通過通道來實現兩個應用程序域之間對象的通信。.NET Remoting中包括4中通道類型:

  1. TcpChannel:Tcp通道使用Tcp協議來跨越.Net Remoting邊界來傳輸序列化的消息流,TcpChannel默認使用二進制格式序列化消息對象,因此具有更高的傳輸性能,但不提供任何內置的安全功能。
  2. HttpChannel:Http通道使用Http協議在客戶端和服務器之間發生消息,使其在Internet上穿越防火牆來傳輸序列化的消息流(這里准確講不能說穿越,主要是因為防火牆都開放了80端口,所以使用Http協議可以穿過防火牆進行數據的傳輸,如果防火牆限制了80端口,Http協議也照樣不能穿越防火牆)。默認情況下,HttpChannel使用Soap格式序列化消息對象,因此它具有更好的互操作性,並且可以使用Http協議中的加密機制來對消息進行加密來保證安全性。因此,通常在局域網內,我們更多地使用TcpChannel,如果要穿越防火牆,則使用HttpChannel。
  3. IpcChannel:進程間通信,只使用同一個系統進程之間的通信,不需要主機名和端口號。而使用Http通道和Tcp通道都要指定主機名和端口號。
  4. 自定義通道:自定義的傳輸通道可以使用任何基本的傳輸協議來進行通信,如UDP協議、SMTP協議等。

三、利用.NET Remoting技術開發分布式應用三部曲

   前面詳細介紹了.NET Remoting相關內容,下面具體看看如何使用.NET Remoting技術來開發分布式應用程序。開發.NET Remoting應用分三步走。

第一步:創建遠程對象,該對象必須繼承MarshalByRefObject對象。具體的示例代碼如下:

 1 namespace RemotingObject
 2 {
 3     // 第一步:創建遠程對象
 4     // 創建遠程對象——必須繼承MarshalByRefObject,該類支持對象的跨域邊界訪問
 5     public class MyRemotingObject :MarshalByRefObject
 6     {
 7         // 用來測試Tcp通道 
 8         public int AddForTcpTest(int a, int b)
 9         {
10             return a + b;
11         }
12 
13         // 用來測試Http通道
14         public int MinusForHttpTest(int a, int b)
15         {
16             return a - b;
17         }
18 
19         // 用來測試IPC通道
20         public int MultipleForIPCTest(int a, int b)
21         {
22             return a * b;
23         }
24     }
25 }

  遠程對象分別定義3個方法,目的是為了測試3中不同的通道方式的效果。

第二步:創建服務器端,需要添加System.Runtime.Remoting.dll引用,具體實現代碼如下所示:

 1 using System;
 2 using System.Runtime.Remoting;
 3 using System.Runtime.Remoting.Channels;
 4 using System.Runtime.Remoting.Channels.Http;
 5 using System.Runtime.Remoting.Channels.Ipc;
 6 using System.Runtime.Remoting.Channels.Tcp;
 7 
 8 namespace RemotingServerHost
 9 {
10     // 第二步:創建宿主應用程序
11     class Server
12     {
13         static void Main(string[] args)
14         {
15             // 1.創建三種通道
16 
17             // 創建Tcp通道,端口號9001
18             TcpChannel tcpChannel = new TcpChannel(9001);
19             
20             // 創建Http通道,端口號9002
21             HttpChannel httpChannel = new HttpChannel(9002);
22 
23             // 創建IPC通道,端口號9003
24             IpcChannel ipcChannel = new IpcChannel("IpcTest");
25 
26             // 2.注冊通道
27             ChannelServices.RegisterChannel(tcpChannel, false);
28             ChannelServices.RegisterChannel(httpChannel, false);
29             ChannelServices.RegisterChannel(ipcChannel, false);
30 
31             // 打印通道信息
32             // 打印Tcp通道的名稱
33             Console.WriteLine("The name of the TcpChannel is {0}", tcpChannel.ChannelName);
34             // 打印Tcp通道的優先級
35             Console.WriteLine("The priority of the TcpChannel is {0}", tcpChannel.ChannelPriority);
36 
37             Console.WriteLine("The name of the HttpChannel is {0}", httpChannel.ChannelName);
38             Console.WriteLine("The priority of the httpChannel is {0}", httpChannel.ChannelPriority);
39 
40             Console.WriteLine("The name of the IpcChannel is {0}", ipcChannel.ChannelName);
41             Console.WriteLine("The priority of the IpcChannel is {0}", ipcChannel.ChannelPriority);
42 
43             // 3. 注冊對象
44             // 注冊MyRemotingObject到.NET Remoting運行庫中
45             RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject.MyRemotingObject), "MyRemotingObject", WellKnownObjectMode.Singleton);
46             Console.WriteLine("Press any key to exit");
47             Console.ReadLine();
48         }
49     }
50 }

第三步:創建客戶端程序,具體的實現代碼如下所示:

 1 using RemotingObject;
 2 using System;
 3 
 4 namespace RemotingClient
 5 {
 6     class Client
 7     {
 8         static void Main(string[] args)
 9         {
10             // 使用Tcp通道得到遠程對象
11             //TcpChannel tcpChannel = new TcpChannel();
12             //ChannelServices.RegisterChannel(tcpChannel, false);
13             MyRemotingObject proxyobj1 = Activator.GetObject(typeof(MyRemotingObject), "tcp://localhost:9001/MyRemotingObject") as MyRemotingObject;
14             if (proxyobj1 == null)
15             {
16                 Console.WriteLine("連接TCP服務器失敗");
17             }
18 
19             //HttpChannel httpChannel = new HttpChannel();
20             //ChannelServices.RegisterChannel(httpChannel, false);
21             MyRemotingObject proxyobj2 = Activator.GetObject(typeof(MyRemotingObject), "http://localhost:9002/MyRemotingObject") as MyRemotingObject;
22             if (proxyobj2 == null)
23             {
24                 Console.WriteLine("連接Http服務器失敗");
25             }
26 
27             //IpcChannel ipcChannel = new IpcChannel();
28             //ChannelServices.RegisterChannel(ipcChannel, false);
29             MyRemotingObject proxyobj3 = Activator.GetObject(typeof(MyRemotingObject), "ipc://IpcTest/MyRemotingObject") as MyRemotingObject;
30             if (proxyobj3 == null)
31             {
32                 Console.WriteLine("連接Ipc服務器失敗");
33             }
34             // 輸出信息
35             Console.WriteLine("This call object by TcpChannel, 100 + 200 = {0}", proxyobj1.AddForTcpTest(100, 200));
36             Console.WriteLine("This call object by HttpChannel, 100 - 200 = {0}", proxyobj2.MinusForHttpTest(100, 200));
37             Console.WriteLine("This call object by IpcChannel, 100 * 200 = {0}", proxyobj1.MultipleForIPCTest(100, 200));
38             Console.WriteLine("Press any key to exit!");
39             Console.ReadLine();
40         }
41     }
42 }

  經過上面的三步,我們就完成了這個分布式應用的開發工作,下面測試下該程序是否可以正常運行,首先,運行服務器端,你將看到如下界面:

  在.NET Remoting中,是允許同時創建多個通道的,但是.NET Remoting要求通道的名字必須不同,因為名字是用來標識通道的唯一標識符。但上面代碼中,我們並沒有指明通道的名字,為什么還可以允許成功呢?從上面圖片可知,當我們創建通道時,如果沒有為其顯式指定通道名,則會使用對應的通道類型作為該通道名,如TcpChannel將會以tcp作為通道名,如果想注冊多個Tcp通道則必須顯式指定其名字。

  下面看看運行客戶端所獲得的結果,具體客戶端運行效果如下圖所示:

 四、使用配置文件來重寫上面的分布式程序

  在第三部分中,我們是把服務器的各種通道方式和地址寫死在程序中的,這樣的實現方式部署起來不方便,下面使用配置文件的方式來配置服務器端的通道類型和服務器地址。  遠程對象的定義不需要改變,下面直接看服務器端使用配置文件后的實現代碼如下所示:

 1 using System;
 2 using System.Runtime.Remoting;
 3 using System.Runtime.Remoting.Channels;
 4 
 5 namespace RemotingServerHostByConfig
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             RemotingConfiguration.Configure("RemotingServerHostByConfig.exe.config", false);
12 
13             foreach (var channel in ChannelServices.RegisteredChannels)
14             {
15                 // 打印通道的名稱
16                 Console.WriteLine("The name of the Channel is {0}", channel.ChannelName);
17                 // 打印通道的優先級
18                 Console.WriteLine("The priority of the Channel is {0}", channel.ChannelPriority);
19             }
20             Console.WriteLine("按任意鍵退出……");
21             Console.ReadLine();
22         }
23     }
24 }

  服務端的配置文件的內容為:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <!--服務端App.config的內容-->
 3 <configuration>
 4     <startup> 
 5         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
 6     </startup>
 7   <system.runtime.remoting>
 8     <application>
 9       <service>
10         <wellknown  mode="Singleton"
11                     type="RemotingObject.MyRemotingObject,RemotingObject"
12                     objectUri="MyRemotingObject"/>
13       </service>
14       <channels>
15         <channel port="9001" ref="tcp"/>
16         <channel port="9002" ref="http"/>
17         <channel portName="IpcTest" ref="ipc"/> <!--Ipc通道不需要端口號-->
18       </channels>
19     </application>
20   </system.runtime.remoting>
21 </configuration>

  此時,客戶端程序的實現代碼如下所示:

 1 using RemotingObject;
 2 using System;
 3 using System.Runtime.Remoting;
 4 
 5 namespace RemotingClientByConfig
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             //使用HTTP通道得到遠程對象
12             RemotingConfiguration.Configure("RemotingClientByConfig.exe.config", false);
13             MyRemotingObject proxyobj1 = new MyRemotingObject();
14             if (proxyobj1 == null)
15             {
16                 Console.WriteLine("連接服務器失敗");
17             }
18 
19             Console.WriteLine("This call object by TcpChannel, 100 + 200 = {0}", proxyobj1.AddForTcpTest(100, 200));
20             Console.WriteLine("This call object by HttpChannel, 100 - 200 = {0}", proxyobj1.MinusForHttpTest(100, 200));
21             Console.WriteLine("This call object by IpcChannel, 100 * 200 = {0}", proxyobj1.MultipleForIPCTest(100, 200));
22             Console.WriteLine("Press any key to exit!");
23             Console.ReadLine();
24         }
25     }
26 }

  客戶端配置文件為:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3     <startup> 
 4         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
 5     </startup>
 6   <system.runtime.remoting>
 7     <application>
 8       <client>
 9         <wellknown  type="RemotingObject.MyRemotingObject,RemotingObject"
10                     url="http://localhost:9002/MyRemotingObject" />
11       </client>
12       <channels>    
13         <channel ref="tcp" port="0"></channel>
14         <channel ref="http" port="0"></channel>
15         <channel ref="ipc" port="0"></channel>
16       </channels>
17     </application>
18   </system.runtime.remoting>
19 </configuration>

  使用配置文件修改后的分布式程序的運行結果與前面的運行結果一樣,這里就不一一貼圖了。

五、總結

   到這里,.NET Remoting技術的分享就結束了,本文只是對.NET Remoting技術做了一個基本的介紹,如果想深入了解.NET Remoting技術的話,推薦大家可以看看下面的專題細細品味C#——.Net Remoting專題。在下一篇文章中,繼續為大家分享另一種分布式技術——Web Service。

   本文的示例代碼文件下載:.NETRemotingSample

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM