所謂.NET Remoting就是跨應(yīng)用程序域邊界調(diào)用程序集。如圖23-16所示,顯示了.NET Remoting應(yīng)用程序的基本構(gòu)架。
從圖23-16中看到,Remoting服務(wù)端承載遠(yuǎn)程對象,使外界能與之通信,對外的信道可以是HTTP、TCP或者IPC。HTTP方式的信道在跨越防火墻上有優(yōu)勢;TCP方式的信道常用在局域網(wǎng)內(nèi)通信,速度比HTTP快很多;IPC信道用于同一臺機(jī)器的進(jìn)程間通信,通信不占用網(wǎng)絡(luò)資源,速度又比TCP快很多。因此,這里的服務(wù)器是一個(gè)廣義的概念,對于TCP和HTTP信道,服務(wù)器可以是兩個(gè)獨(dú)立的物理計(jì)算機(jī)。
那么,最基本的.NET Remoting應(yīng)用程序應(yīng)該由三部分構(gòu)成:
· 服務(wù)端。承載遠(yuǎn)程對象。
· 遠(yuǎn)程對象。需要跨應(yīng)用程序域邊界調(diào)用的程序集。
· 客戶端。用于調(diào)用遠(yuǎn)程對象。
遠(yuǎn)程對象是根本,服務(wù)端只是一個(gè)載體,那么我們就先來創(chuàng)建一個(gè)簡單的遠(yuǎn)程對象:
1.繼續(xù)使用前面的一個(gè)解決方案。右鍵單擊解決方案,選擇“添加”→“新建項(xiàng)目”命令,新建一個(gè)TestRemoteObject類庫項(xiàng)目。
2.把默認(rèn)的Class1.cs重命名為RemoteObject.cs,打開cs文件,修改代碼為:
using System;
namespace RemoteObject
{
public class MyObject : MarshalByRefObject
{
public int Add(int a, int b)
{
return a + b;
}
}
}
在RemoteObject命名空間下有一個(gè)MyObject類,除了繼承MarshalByRefObject類使之能跨應(yīng)用程序域邊界被訪問之外,和一般的類沒有任何區(qū)別。
3.右鍵單擊這個(gè)類庫項(xiàng)目,如圖23-17所示。

圖23-17 項(xiàng)目屬性
我們看到這個(gè)項(xiàng)目的程序集名為TestRemoteObject,默認(rèn)的命名空間為TestRemoteObject。默認(rèn)的命名空間名字和程序集的名字是一樣的,但是在代碼中我們的命名空間名字為RemoteObject,和程序集名字不同以便進(jìn)行區(qū)分。
注意:程序集和命名空間是兩個(gè)不同的概念,一個(gè)程序集可以包括幾個(gè)命名空間,一個(gè)命名空間也可以由多個(gè)程序集來實(shí)現(xiàn)。
在創(chuàng)建了遠(yuǎn)程對象后就需要?jiǎng)?chuàng)建Remoting服務(wù)端來發(fā)布這個(gè)遠(yuǎn)程對象了。
4.服務(wù)端可以是一個(gè)控制臺應(yīng)用程序、Windows應(yīng)用程序、Windows服務(wù)甚至是IIS。為了簡單,我們將首先使用控制臺應(yīng)用程序做服務(wù)端。在解決方案中新建一個(gè)名為TestRemotingConsoleServer的控制臺應(yīng)用程序,然后右鍵單擊項(xiàng)目,選擇添加應(yīng)用,如圖23-18所示,添加System.Runtime.Remoting的引用。
5.把Program.cs修改成如下:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace TestRemotingConsoleServer
{
class Program
{
static void Main(string[] args)
{
// 新建一個(gè)TCP信道
TcpChannel tc = new TcpChannel(9999);
// 注冊TCP信道
ChannelServices.RegisterChannel(tc, false);
// 注冊知名對象
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject.
MyObject), "myObject", WellKnownObjectMode.SingleCall);
// 讓控制臺不會自動關(guān)閉
Console.ReadLine();
}
}
}

圖23-18 添加System.Runtime.Remoting的引用
我們看到,使用.NET Remoting發(fā)布遠(yuǎn)程對象并不復(fù)雜,首先需要告知程序使用哪種信道發(fā)布遠(yuǎn)程對象。在這里我們選擇TCP信道,并在9999端口通信。然后要告知程序把對象注冊為哪種類型,在這里筆者不想詳細(xì)闡述遠(yuǎn)程對象的種類和模式,讀者只需要理解在這里我們把Remote- Object.MyObject這個(gè)類型使用一個(gè)固定的名字myObject來發(fā)布(因此叫做知名對象),對象的模式是SingleCall,SingleCall模式的對象是無狀態(tài)的。
最后我們來完成用客戶端應(yīng)用程序調(diào)用遠(yuǎn)程對象??蛻舳藨?yīng)用程序可以是ASP.NET應(yīng)用程序、控制臺應(yīng)用程序或者Windows應(yīng)用程序。那么,我們就直接使用前一節(jié)建立的ASP.NET應(yīng)用程序作為客戶端吧。
6.在TestWeb網(wǎng)站下新建一個(gè)RemotingTest.aspx,然后在頁面的Page_Load事件處理方法中調(diào)用遠(yuǎn)程對象。
protected void Page_Load(object sender, EventArgs e)
{
RemoteObject.MyObject mo = (RemoteObject.MyObject)Activator.GetObject
(typeof(RemoteObject.MyObject), "tcp://localhost:9999/myObject");
Response.Write(mo.Add(1, 2));
}
在這里,我們從遠(yuǎn)程地址tcp://localhost:9999/myObject創(chuàng)建遠(yuǎn)程對象,并調(diào)用了對象的Add()方法。myObject就是在服務(wù)端中為知名對象起的名字。
7.編譯整個(gè)解決方案,IDE提示“找不到命名空間RemoteObject”,這是因?yàn)槲覀兊目蛻舳撕头?wù)端項(xiàng)目沒有引用遠(yuǎn)程對象類庫項(xiàng)目。右鍵單擊服務(wù)端項(xiàng)目,選擇“添加引用”,在項(xiàng)目頁中找到類庫項(xiàng)目,單擊“確定”按鈕,如圖23-19所示。
對于客戶端項(xiàng)目也同樣添加類庫的引用,然后重新編譯解決方案。
8.現(xiàn)在就能進(jìn)行測試了。解決方案中的項(xiàng)目如圖23-20所示。

圖23-19 添加項(xiàng)目引用 圖23-20 解決方案中的項(xiàng)目
要讓遠(yuǎn)程調(diào)用成功運(yùn)行,先要啟動服務(wù)端使之監(jiān)聽端口。如圖23-20所示,單擊控制臺應(yīng)用程序,項(xiàng)目名自動以粗體標(biāo)識,表示這是當(dāng)前項(xiàng)目,按Ctrl+F5組合鍵直接啟動程序。然后再單擊TestWeb網(wǎng)站,右鍵單擊RemotingTest.aspx,選擇設(shè)為起始頁,按Ctrl+F5組合鍵啟動網(wǎng)站。
如圖23-21所示,頁面顯示3,成功了!

圖23-21 調(diào)用遠(yuǎn)程對象
注意圖23-21所示,在整個(gè)過程中需要確保服務(wù)端處于運(yùn)行狀態(tài)。至此,我們完成了第一個(gè).NET Remoting應(yīng)用程序。
23.3.2 Remoting的信道
前面提到過,Remoting有多種信道可以選擇,這大大增加了我們分布式系統(tǒng)的靈活性。如果希望在廣域網(wǎng)通信,可以使用HTTP信道,如果希望在局域網(wǎng)通信取得更好的性能,可以使用TCP信道,如果希望在本機(jī)上的不同進(jìn)程間通信以獲得最好的性能,可以使用IPC信道。
下面我們來修改前面的程序,使之使用三種不同的Remoting信道,并且我們要比較三種信道在效率上差多少:
1.首先在遠(yuǎn)程對象中新增一個(gè)方法,使之返回大量的數(shù)據(jù)。
using System;
namespace RemoteObject
{
public class MyObject : MarshalByRefObject
{
public int Add(int a, int b)
{
return a + b;
}
public string[] GetData()
{
string[] data = new string[100000];
for (int i = 0; i < data.Length; i++)
data[i] = "很大量的數(shù)據(jù)"+i;
return data;
}
}
}
2.然后修改服務(wù)端,使之在三個(gè)不同的信道上發(fā)布遠(yuǎn)程對象。
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Ipc;
namespace TestRemotingConsoleServer
{
class Program
{
static void Main(string[] args)
{
// 新建一個(gè)TCP信道
TcpChannel tc = new TcpChannel(9999);
// 新建一個(gè)HTTP信道
HttpChannel hc = new HttpChannel(8888);
// 新建一個(gè)IPC信道
IpcChannel ic = new IpcChannel("testPipe");
// 注冊TCP信道
ChannelServices.RegisterChannel(tc, false);
ChannelServices.RegisterChannel(hc, false);
ChannelServices.RegisterChannel(ic, false);
// 注冊知名對象
RemotingConfiguration.RegisterWellKnownServiceType(typeof
(RemoteObject.MyObject), "myObject", WellKnownObjectMode.SingleCall);
// 讓控制臺不會自動關(guān)閉
Console.ReadLine();
}
}
}
注意,由于是在同一個(gè)機(jī)器上注冊多個(gè)信道,需要給每個(gè)信道使用不同的端口。對于IPC信道來說,使用一個(gè)管道名來區(qū)分而不是端口號。為了能更好地測試三者的差別,我們把服務(wù)端部署到另外一個(gè)服務(wù)器上(把EXE文件和DLL文件復(fù)制過去)。
3.在RemotingTest.aspx頁面上新建三個(gè)按鈕用于使用不同的信道調(diào)用遠(yuǎn)程對象。
<asp:Button ID="btn_HttpChannel" runat="server" Text="http通道"/>
<asp:Button ID="btn_TcpChannel" runat="server" Text="tcp通道"/>
<asp:Button ID="btn_IpcChannel" runat="server" Text="ipc通道"/>
按鈕的單擊事件處理方法如下:
protected void btn_HttpChannel_Click(object sender, EventArgs e)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
RemoteObject.MyObject mo = (RemoteObject.
MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),
"http://srv-devapphost:8888/myObject");
Response.Write("HTTP信道<br/>");
Response.Write(string.Format("記錄數(shù):{0}條<br/>", mo.GetData().Length));
Response.Write(string.Format("花費(fèi)時(shí)間:{0}毫秒<br/>", sw.ElapsedMilliseconds));
}
protected void btn_TcpChannel_Click(object sender, EventArgs e)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
RemoteObject.MyObject mo = (RemoteObject.MyObject)Activator.GetObject
(typeof(RemoteObject.MyObject), "tcp://srv-devapphost:9999/myObject");
Response.Write("TCP信道<br/>");
Response.Write(string.Format("記錄數(shù):{0}條<br/>", mo.GetData().Length));
Response.Write(string.Format("花費(fèi)時(shí)間:{0}毫秒<br/>", sw.ElapsedMilliseconds));
}
protected void btn_IpcChannel_Click(object sender, EventArgs e)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
RemoteObject.MyObject mo = (RemoteObject.
MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),
"ipc://testPipe/myObject");
Response.Write("IPC信道<br/>");
Response.Write(string.Format("記錄數(shù):{0}條<br/>", mo.GetData().Length));
Response.Write(string.Format("花費(fèi)時(shí)間:{0}毫秒<br/>", sw.ElapsedMilliseconds));
}
可以看到,使用三種信道調(diào)用的代碼僅在URL上有區(qū)別。在這里我們不但把服務(wù)端部署到了遠(yuǎn)程服務(wù)器上,而且在本地也開了一個(gè)服務(wù)端用于在IPC上注冊遠(yuǎn)程對象。
4.測試結(jié)果如圖23-22所示。
  
圖23-22 Remoting的三種信道
可以看到在效率上三者有明顯的差別。IPC比TCP快是因?yàn)樗鼈鬟f數(shù)據(jù)不經(jīng)過網(wǎng)絡(luò),不占用網(wǎng)絡(luò)資源。TCP比HTTP快很多是因?yàn)槟J(rèn)情況下TCP信道使用二進(jìn)制序列化,序列化后的數(shù)據(jù)量很小,而HTTP默認(rèn)使用SOAP消息進(jìn)行格式化,基于XML的SOAP消息非常臃腫,因此在傳輸上會比TCP花費(fèi)更多的時(shí)間。不過不可否認(rèn)HTTP信道在跨防火墻上的優(yōu)勢,因此使用哪種信道還需要根據(jù)自己的需求來選擇。
23.3.3 使用配置文件增加靈活性
雖然我們做的Remoting程序可以正常使用,但是整個(gè)程序非常不靈活:
· 服務(wù)端有關(guān)信道、端口等的配置都直接寫死在程序里面。
· 客戶端設(shè)置的遠(yuǎn)程對象的地址也是寫死在程序里面的。
對于客戶端的配置不是大問題,因?yàn)槠鋵?shí)那個(gè)URL就是一個(gè)字符串。而服務(wù)端的配置文件應(yīng)該怎么做呢?其實(shí)一點(diǎn)也不復(fù)雜,添加一個(gè)app.config然后寫入下面的內(nèi)容:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="RemoteHostService">
<service>
<wellknown type="RemoteObject.MyObject, TestRemoteObject" objectUri=
"myObject" mode="SingleCall" />
</service>
<channels>
<channel ref="tcp" port="9999" />
<channel ref="http" port="8888" />
<channel ref="ipc" portName="testPipe" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
可以看到配置文件主要由兩部分構(gòu)成:
· 定義遠(yuǎn)程對象類型的service節(jié)點(diǎn)。在這里我們定義了一個(gè)知名對象,模式是SingleCall,對象名為myObject。
· 定義信道的channels節(jié)點(diǎn)。在這里定義了三個(gè)信道,和先前程序方式定義的一樣。
特別需要注意的是,這里的type="RemoteObject.MyObject,TestRemoteObject",格式是:
type="命名空間.類型名,程序集名"
對比圖23-17看看,現(xiàn)在你知道為什么當(dāng)時(shí)筆者要把命名空間、類型和程序集三者的名字設(shè)置不同了吧。那么,怎么讓服務(wù)端加載配置文件讀取Remoting的配置呢?只需要一行代碼就行。
RemotingConfiguration.Configure("TestRemotingConsoleServer.exe.config", false);
Console.ReadLine();
你可能會奇怪,配置文件是app.config,為什么這里寫成了應(yīng)用程序名.config呢?其實(shí)在編譯的時(shí)候IDE會自動把配置文件進(jìn)行改名,以免發(fā)生沖突,如圖23-23所示,可以看到Release目錄的 文件。

圖23-23 服務(wù)端程序release文件夾
真正有用的是加亮的三個(gè)文件(分別是遠(yuǎn)程對象、服務(wù)端和配置文件),在部署的時(shí)候只需要復(fù)制這些文件即可。
雖然改了服務(wù)端,但是我們并沒有改變通道的端口,因此客戶端不需要做任何修改就能直接運(yùn)行。如果你希望把URL從程序中分離的話,可以在配置文件中添加幾個(gè)節(jié)點(diǎn)。
<appSettings>
<add key="HTTPChannel" value="http://localhost:8888/myObject"/>
<add key="TCPChannel" value="tcp://localhost:9999/myObject"/>
<add key="IPCChannel" value="ipc://testPipe/myObject"/>
</appSettings>
然后在代碼中調(diào)用配置文件讀取URL。
RemoteObject.MyObject mo = (RemoteObject.MyObject)
Activator.GetObject(typeof(RemoteObject.MyObject),
ConfigurationManager.AppSettings["HTTPChannel"]);
其他兩個(gè)信道的代碼差不多,就不列出來了?,F(xiàn)在這樣就非常靈活了,修改信道、修改端口甚至轉(zhuǎn)移服務(wù)端的位置只需要重新調(diào)整配置文件即可。
23.3.4 使用接口降低耦合
讀者首先要明確一點(diǎn),客戶端調(diào)用的遠(yuǎn)程方法是在服務(wù)端執(zhí)行的。如下,我們在遠(yuǎn)程對象中增加一個(gè)方法。
public void HelloWorld()
{
Console.WriteLine("編程快樂");
}
重新編譯服務(wù)端和客戶端,運(yùn)行客戶端可以看到服務(wù)端控制臺程序上輸出了“編程快樂”字樣,如圖23-24所示。
那么問題就來了,既然遠(yuǎn)程對象是在服務(wù)端執(zhí)行的,客戶端為什么要引用遠(yuǎn)程對象呢?假設(shè)我們的報(bào)表系統(tǒng)是使用.NET Remoting開發(fā)的,難道要把核心DLL也公布給客戶嗎(要知道.NET應(yīng)用程序是很容易被反編譯得到“源代碼”的)?其實(shí),客戶端只需要得到遠(yuǎn)程對象的“描述”,知道遠(yuǎn)程對象的類型以及成員定義,讓客戶端代碼能編譯通過即可。具體方法是什么,怎么實(shí)現(xiàn),客戶端并不關(guān)心。
那么,怎么構(gòu)建這個(gè)供客戶端使用的殼子呢?有兩種方法。
· 直接使用工具比如soapsuds.exe來生成。
· 使用基于接口的編程方法。
由于篇幅關(guān)系,在這里我們僅僅介紹第二種方法的實(shí)現(xiàn):
1.新建一個(gè)類庫項(xiàng)目ITestRemoteObject,這個(gè)類庫是前面TestRemoteObject的接口(Interface),因此以字母I開頭。
2.打開TestRemoteObject下的RemoteObject.cs,把鼠標(biāo)放在MyObject類上單擊右鍵,選擇“重構(gòu)”→“提取接口”,如圖23-25所示。
單擊“全選”按鈕選中所有成員,單擊“確定”按鈕??梢钥吹絋estRemoteObject類庫下面多了一個(gè)cs文件,如圖23-26所示。

圖23-25 提取接口 圖23-26 自動生成的接口
IMyObject就是MyObject類對應(yīng)的接口。打開這個(gè)文件可以看到接口其實(shí)就是對類成員的定義,沒有實(shí)際的實(shí)現(xiàn)。
using System;
namespace TestRemoteObject
{
interface IMyObject
{
int Add(int a, int b);
string[] GetData();
void HelloWorld();
}
}
對這個(gè)接口我們要進(jìn)行一些改動:
· 要讓接口能被外部調(diào)用,需要把接口加上公有訪問修飾符。
· 系統(tǒng)自動以程序集的名字作為命名空間的命名,我們還是改回原來的RemoteObject。
using System;
namespace RemoteObject
{
public interface IMyObject
{
int Add(int a, int b);
string[] GetData();
void HelloWorld();
}
}
3.現(xiàn)在這個(gè)接口在遠(yuǎn)程對象文件中,我們需要把它移動到ITestRemoteObject中,直接點(diǎn)擊文件,Ctrl+X(剪切)、CTRL+V(粘貼)即可。
4.回頭看MyObject文件:
public class MyObject : MarshalByRefObject, TestRemoteObject.IMyObject
系統(tǒng)自動讓它繼承了TestRemoteObject.IMyObject,剛才我們把TestRemoteObject修改成了RemoteObject,現(xiàn)在這里也需要同樣修改。
既然讓類實(shí)現(xiàn)接口,那么就需要讓TestRemoteObject項(xiàng)目引用ITestRemoteObject項(xiàng)目。右鍵單擊TestRemoteObject項(xiàng)目,選擇添加引用,在項(xiàng)目選項(xiàng)卡中找到ITestRemoteObject項(xiàng)目,單擊“確定”按鈕即可。
現(xiàn)在兩個(gè)項(xiàng)目的結(jié)構(gòu)應(yīng)該如圖23-27所示。
你可能會問,接口僅僅是對類的一個(gè)定義嗎?不僅僅是這樣,接口還對類有約束力,如果你修改了接口也一定要修改“實(shí)現(xiàn)”。如果你在接口中新加入一個(gè)Test()的方法,而不修改“實(shí)現(xiàn)”,編譯程序會得到編譯錯(cuò)誤,如圖23-28所示。

圖23-27 基于接口的編程 圖23-28 類需要實(shí)現(xiàn)接口的成員
5.現(xiàn)在,我們的客戶端就可以引用和使用接口,而不是直接引用和使用遠(yuǎn)程對象了。首先右鍵單擊TestWeb網(wǎng)站,選擇屬性頁。在引用頁找到原來的遠(yuǎn)程對象TestRemoteObject,刪除它的引用,并添加ITestRemoteObject的引用,如圖23-29所示。

圖23-29 修改網(wǎng)站項(xiàng)目的引用
查找替換Remoting.aspx.cs中的所有RemoteObject. MyObject為RemoteObject.IMyObject,比如:
RemoteObject.IMyObject mo = (RemoteObject.IMyObject)Activator.
GetObject(typeof(RemoteObject.IMyObject), ConfigurationManager.
AppSettings["TCPChannel"]);
mo.HelloWorld();
6.重新編譯解決方案,先后運(yùn)行服務(wù)端和客戶端,效果和原來的沒有什么不同。但是,這樣的方式更靈活了,或者說耦合更低了。為什么這樣說呢?因?yàn)?,現(xiàn)在如果希望在服務(wù)端的實(shí)現(xiàn)中做什么改動的話,不需要重新編譯和部署客戶端程序。
23.3.5 使用Windows服務(wù)承載遠(yuǎn)程對象
現(xiàn)在的程序看似很完美,但是要想真正應(yīng)用還有一些問題。我們的服務(wù)端是一個(gè)控制臺應(yīng)用程序,如果在服務(wù)器上需要有10個(gè)Remoting的服務(wù)端,那么我們服務(wù)器重啟動后也需要重啟動這10個(gè)程序嗎?讀者可能會說可以把它們加入開始菜單的啟動中讓程序自動啟動。但是你有沒有想過,在登錄到服務(wù)器進(jìn)行維護(hù)的時(shí)候很容易不小心把控制臺程序關(guān)閉了,而且關(guān)閉之后還不知道。
要想解決這個(gè)問題就需要使用一種后臺式的程序來作為服務(wù)端,Windows服務(wù)正好可以滿足這個(gè)要求,而且還可以設(shè)置Windows服務(wù)自動啟動。使用VS 2005創(chuàng)建.NET的Windows服務(wù)非常簡單,下面我們一起來實(shí)現(xiàn)Windows服務(wù)版本的Remoting服務(wù)端。
1.創(chuàng)建一個(gè)新的Windows服務(wù)項(xiàng)目TestRemotingService,如圖23-30所示。

圖23-30 創(chuàng)建新的Windows服務(wù)項(xiàng)目
2.打開Service1代碼視圖,找到OnStart部分,加入代碼。
protected override void OnStart(string[] args)
{
System.Runtime.Remoting.RemotingConfiguration.Configure(AppDomain.CurrentDomain.
BaseDirectory + "TestRemotingService.exe.config", false);
}
這句代碼實(shí)現(xiàn)在Windows服務(wù)啟動的時(shí)候從Windows服務(wù)安裝目錄所在的配置文件加載Remoting配置,然后把先前控制臺服務(wù)端的配置文件復(fù)制過來。
現(xiàn)在這個(gè)Windows服務(wù)是Remoting的服務(wù)端,因此也別忘記添加對TestRemoteObject遠(yuǎn)程對象的引用。
3.切換到Service1的設(shè)計(jì)視圖,在空白處右鍵單擊,然后選擇“添加安裝程序”選項(xiàng)。如圖23-31所示。

圖23-31 添加服務(wù)安裝程序
4.打開系統(tǒng)自動生成的ProjectInstaller.cs,如圖23-32所示,可以看到頁面上有兩個(gè)組件。

圖23-32 服務(wù)安裝程序
單擊serviceProcessInstaller1組件,觀察屬性窗口,如圖23-33所示。
在這里我們把Account屬性設(shè)置為LocalSystem,作為服務(wù)的賬戶類型。然后單擊serviceInstaller1組件,觀察屬性窗口,如圖23-34所示。

圖23-33 ServiceProcessInstaller組件 圖23-34 ServiceInstaller組件
在這里可以設(shè)置服務(wù)友好名、服務(wù)的描述、服務(wù)名和啟動方式。只需要把StartType設(shè)置為Automatic,服務(wù)就能在系統(tǒng)重新啟動后自動啟動。
5.現(xiàn)在就可以安裝服務(wù)了,單擊“開始”菜單→“所有程序”→Microsoft Visual Studio 2005→Visual Studio Tools→“Visual Studio 2005命令行提示”,如圖23-35所示,使用installutil程序來安裝Windows服務(wù)。

圖23-35 使用installutil工具安裝Windows服務(wù)
如果你覺得輸入exe所在路徑太麻煩,可以直接打開文件夾把exe文件拖入命令行窗口。卸載服務(wù)使用–u參數(shù)。
installutil -u Windows服務(wù)exe所在路徑
6.執(zhí)行“我的電腦右鍵”→“管理”→“服務(wù)和應(yīng)用程序”→“服務(wù)”命令。如圖23-36所示,可以在列表中找到我們的服務(wù)。

圖23-36 服務(wù)已經(jīng)安裝成功
查看這個(gè)服務(wù)的屬性,如圖23-37所示。

圖23-37 Windows服務(wù)的屬性
7.如果程序?qū)懙臎]有什么問題的話(其實(shí)我們只寫了一行代碼),服務(wù)應(yīng)該能正常啟動,然后可以打開網(wǎng)站進(jìn)行測試。
注意:由于安全問題,必須為Windows服務(wù)指定一個(gè)有效賬戶(Account=User)才能使用IPC信道,在這里就不詳細(xì)敘述了。
除了使用Windows服務(wù)承載遠(yuǎn)程對象外,還可以使用IIS。不過需要注意,使用IIS承載遠(yuǎn)程對象只能在HTTP信道上通信,好處在于可以使用IIS來進(jìn)行安全管理。需要說的是,HTTP方式的Remoting效率非常低(甚至不如Web Service),因此不推薦。具體實(shí)現(xiàn)IIS部署Remoting的方法在這里就不說明了。
23.3.6 異步操作
在介紹Web服務(wù)的時(shí)候,我們介紹了異步調(diào)用Web服務(wù)的操作,在這里我們將介紹如何異步調(diào)用遠(yuǎn)程對象的方法。
1.首先在遠(yuǎn)程對象中加一個(gè)耗時(shí)2秒的方法。
public string LongWork()
{
System.Threading.Thread.Sleep(2000);
return "編程快樂";
}
別忘記同時(shí)更新接口。
using System;
namespace RemoteObject
{
public interface IMyObject
{
int Add(int a, int b);
string[] GetData();
void HelloWorld();
string LongWork();
}
}
2.在客戶端RemotingTest.asp上添加一個(gè)按鈕,按鈕的單擊事件處理方法如下:
protected void btn_AsyncInvoke_Click(object sender, EventArgs e)
{
sw = new System.Diagnostics.Stopwatch();
sw.Start();
RemoteObject.IMyObject mo = (RemoteObject.IMyObject)
Activator.GetObject(typeof(RemoteObject.IMyObject), ConfigurationManager.
AppSettings["TCPChannel"]);
MyDelegate md = new MyDelegate(mo.LongWork);
AsyncCallback ac = new AsyncCallback(this.CallBack);
IAsyncResult Iar = md.BeginInvoke(ac, null);
System.Threading.Thread.Sleep(1000);
}
在這里使用了兩個(gè)私有變量,一個(gè)是用于Stopwatch,另外一個(gè)是方法的代理,在Page_Load上 添加。
private delegate string MyDelegate();
private System.Diagnostics.Stopwatch sw;
在調(diào)用了異步方法后線程休息了1秒,異步方法完成之后會調(diào)用回調(diào)方法。
public void CallBack(IAsyncResult Iar)
{
if (Iar.IsCompleted)
{
Response.Write("異步調(diào)用<br/>");
Response.Write(string.Format("花費(fèi)時(shí)間:{0}毫秒<br/>", sw.
ElapsedMilliseconds));
}
}
3.由于方法是異步調(diào)用的,方法執(zhí)行2秒,我們當(dāng)前的按鈕單擊處理事件占用1秒,總共占用的時(shí)間也是2秒,如圖23-38所示。

圖23-38 異步調(diào)用方法
異步調(diào)用遠(yuǎn)程對象的方法和異步調(diào)用本地對象的方法其實(shí)差不多,在這里就不詳述了。
|