濮阳杆衣贸易有限公司

主頁(yè) > 知識(shí)庫(kù) > Microsoft .Net Remoting系列教程之三:Remoting事件處理全接觸

Microsoft .Net Remoting系列教程之三:Remoting事件處理全接觸

熱門標(biāo)簽:蘇州如何辦理400電話 西寧呼叫中心外呼系統(tǒng)線路商 臨沂智能電話機(jī)器人加盟 400電話辦理怎么樣 外呼電話機(jī)器人成本 聯(lián)通官網(wǎng)400電話辦理 百應(yīng)電話機(jī)器人外呼系統(tǒng) 地圖標(biāo)注軟件免費(fèi)下載 網(wǎng)絡(luò)電話外呼系統(tǒng)上海

  前言:在Remoting中處理事件其實(shí)并不復(fù)雜,但其中有些技巧需要你去挖掘出來(lái)。正是這些技巧,仿佛森嚴(yán)的壁壘,讓許多人望而生畏,或者是不知所謂,最后放棄了事件在Remoting的使用。關(guān)于這個(gè)主題,在網(wǎng)上也有很多討論,相關(guān)的技術(shù)文章也不少,遺憾的是,很多文章概述的都不太全面。我在研究Remoting的時(shí)候,也對(duì)事件處理發(fā)生了興趣。經(jīng)過(guò)參考相關(guān)的書籍、文檔,并經(jīng)過(guò)反復(fù)的試驗(yàn),深信自己能夠把這個(gè)問(wèn)題闡述清楚了。

  本文對(duì)于Remoting和事件的基礎(chǔ)知識(shí)不再介紹,有興趣的可以看我的系列文章,或查閱相關(guān)的技術(shù)文檔。

本文示例代碼下載:

Remoting事件(客戶端發(fā)傳真)

Remoting事件(服務(wù)端廣播)

Remoting事件(服務(wù)端廣播改進(jìn))

  應(yīng)用Remoting技術(shù)的分布式處理程序,通常包括三部分:遠(yuǎn)程對(duì)象、服務(wù)端、客戶端。因此從事件的方向上看,就應(yīng)該有三種形式:

1、服務(wù)端訂閱客戶端事件
2、客戶端訂閱服務(wù)端事件
3、客戶端訂閱客戶端事件

  服務(wù)端訂閱客戶端事件,即由客戶端發(fā)送消息,服務(wù)端捕捉該消息,然后響應(yīng)該事件,相當(dāng)于下級(jí)向上級(jí)發(fā)傳真。反過(guò)來(lái),客戶端訂閱服務(wù)端事件,則是由服務(wù)端發(fā)送消息,此時(shí),所有客戶端均捕獲該消息,激發(fā)事件,相當(dāng)于是一個(gè)系統(tǒng)廣播。而客戶端訂閱客戶端事件呢?就類似于聊天了。由某個(gè)客戶端發(fā)出消息,其他客戶端捕獲該消息,激發(fā)事件??上У氖?,我并沒(méi)有找到私聊的解決辦法。當(dāng)客戶端發(fā)出消息后,只要訂閱了該事件的,都會(huì)獲得該信息。

  然而不管是哪一種方式,究其實(shí)質(zhì),真正包含事件的還是遠(yuǎn)程對(duì)象。原理很簡(jiǎn)單,我們想一想,在Remoting中,客戶端和服務(wù)端傳遞的內(nèi)容是什么呢?毋庸置疑,是遠(yuǎn)程對(duì)象。因此,我們傳遞的事件消息,自然是被遠(yuǎn)程對(duì)象所包裹。這就像EMS快遞,遠(yuǎn)程對(duì)象是運(yùn)送信件的汽車,而事件消息就是汽車所裝載的信件。至于事件傳遞的方向,只是發(fā)送者和訂閱者的角色發(fā)生了改變而已。

一、 服務(wù)端訂閱客戶端事件

  服務(wù)端訂閱客戶端事件,相對(duì)比較簡(jiǎn)單。我們就以發(fā)傳真為例。首先,我們必須具備傳真機(jī)和要傳真的文件,這就好比我們的遠(yuǎn)程對(duì)象。而且這個(gè)傳真機(jī)上必須具備“發(fā)送”的操作按鈕。這就好比是遠(yuǎn)程對(duì)象中的一個(gè)委托。當(dāng)客戶發(fā)送傳真時(shí),就需要在客戶端上激活一個(gè)發(fā)送消息的方法,這就好比我們按了“發(fā)送”按鈕。消息發(fā)送到服務(wù)端后,觸發(fā)事件,這個(gè)事件正是服務(wù)端訂閱的。服務(wù)端獲得該事件消息后,再處理相關(guān)業(yè)務(wù)。這就好比接收傳真的人員,當(dāng)傳真收到后,會(huì)聽到接通的聲音,此時(shí)選擇“接收”后,該消息就被捕獲了。

現(xiàn)在,我們就來(lái)模擬這個(gè)流程。首先定義遠(yuǎn)程對(duì)象,這個(gè)對(duì)象處理的應(yīng)該是一個(gè)發(fā)送傳真的業(yè)務(wù):
首先是遠(yuǎn)程對(duì)象的公共接口(Common.dll):

public delegate void FaxEventHandler(string fax);
public interface IFaxBusiness
{
 void SendFax(string fax);
}

注意,在公共接口程序集中,定義了一個(gè)公共委托。

然后我們定義具體處理傳真業(yè)務(wù)的遠(yuǎn)程對(duì)象類(FaxBusiness.dll),在這個(gè)類中,先要添加對(duì)公共接口程序集的引用:

public class FaxBusiness:MarshalByRefObject,IFaxBusiness
{ 
 public static event FaxEventHandler FaxSendedEvent;

 #region

 public void SendFax(string fax)
 {
 if (FaxSendedEvent != null)
 {
 FaxSendedEvent(fax);
 }
 }

 #endregion

 public override object InitializeLifetimeService()
 {
 return null;
 }
}

  這個(gè)遠(yuǎn)程對(duì)象中,事件的類型就是我們?cè)诠渤绦蚣疌ommon.dll中定義的委托類型。SendFax實(shí)現(xiàn)了接口IFaxBusiness中的方法。這個(gè)方法的簽名和定義的委托一致,它調(diào)用了事件FaxSendedEvent。
特殊的地方是我們定義的遠(yuǎn)程對(duì)象最好是重寫MarshalByRefObject類的InitializeLifetimeService()方法。返回null值表明這個(gè)遠(yuǎn)程對(duì)象的生命周期為無(wú)限大。為什么要重寫該方法呢?道理不言自明,如果生命周期不進(jìn)行限制的話,一旦遠(yuǎn)程對(duì)象的生命周期結(jié)束,事件就無(wú)法激活了。
接下來(lái)就是分別實(shí)現(xiàn)客戶端和服務(wù)端了。服務(wù)端是一個(gè)Windows應(yīng)用程序,界面如下:

我們?cè)诩虞d窗體的時(shí)候,注冊(cè)通道和遠(yuǎn)程對(duì)象:

private void ServerForm_Load(object sender, System.EventArgs e)
{
 HttpChannel channel = new HttpChannel(8080);
 ChannelServices.RegisterChannel(channel);

 RemotingConfiguration.RegisterWellKnownServiceType(
 typeof(FaxBusiness),"FaxBusiness.soap",WellKnownObjectMode.Singleton);
 FaxBusiness.FaxSendedEvent += new FaxEventHandler(OnFaxSended);
}

  我們采用的是SingleTon模式,注冊(cè)了一個(gè)遠(yuǎn)程對(duì)象。注意看,這段代碼和一般的Remoting服務(wù)端有什么區(qū)別?對(duì)了,它多了一行注冊(cè)事件的代碼:

FaxBusiness.FaxSendedEvent += new FaxEventHandler(OnFaxSended);

這行代碼,就好比我們服務(wù)端的傳真機(jī),一直切換為“自動(dòng)”模式。它會(huì)一直監(jiān)聽著來(lái)自客戶端的傳真信息,一旦傳真信息從客戶端發(fā)過(guò)來(lái)了,則響應(yīng)事件方法,即OnFaxSended方法:

public void OnFaxSended(string fax)
{
 txtFax.Text += fax;
 txtFax.Text += System.Environment.NewLine;
}

這個(gè)方法很簡(jiǎn)單,就是把客戶端發(fā)過(guò)來(lái)的Fax顯示到txtFax文本框控件上。

而客戶端呢?仍然是一個(gè)Windows應(yīng)用程序。代碼非常簡(jiǎn)單,首先為了簡(jiǎn)便其見,我們?nèi)匀蛔屗谘b載窗體的時(shí)候,激活遠(yuǎn)程對(duì)象:

private void ClientForm_Load(object sender, System.EventArgs e)
{
 HttpChannel channel = new HttpChannel(0);
 ChannelServices.RegisterChannel(channel);

 faxBus = (IFaxBusiness)Activator.GetObject(typeof(IFaxBusiness),
 "http://localhost:8080/FaxBusiness.soap");
}

呵呵,可以說(shuō)客戶端激活對(duì)象的方法和普通的Remoting客戶端應(yīng)用程序沒(méi)有什么不同。該寫傳真了!我們?cè)诖绑w上放一個(gè)文本框?qū)ο?,改其Multiline屬性為true。再放一個(gè)按鈕,負(fù)責(zé)發(fā)送傳真:

private void btnSend_Click(object sender, System.EventArgs e)
{
 if (txtFax.Text != String.Empty)
 {
 string fax = "來(lái)自" + GetIpAddress() + "客戶端的傳真:"
 + System.Environment.NewLine;
 fax += txtFax.Text;
 faxBus.SendFax(fax);
 }
 else
 {
 MessageBox.Show("請(qǐng)輸入傳真內(nèi)容!");
 }
}

private string GetIpAddress()
{ 
 IPHostEntry ipHE = Dns.GetHostByName(Dns.GetHostName());
 return ipHE.AddressList[0].ToString(); 
}

  在這個(gè)按鈕單擊事件中,只需要調(diào)用遠(yuǎn)程對(duì)象faxBus的SendFax()方法就OK了,非常簡(jiǎn)單??墒锹瑸槭裁茨愕拇a有這么多行???其實(shí),沒(méi)有什么奇怪的,我只是想到發(fā)傳真的客戶可能會(huì)很多。為了避免服務(wù)端人員犯糊涂,搞不清楚是誰(shuí)發(fā)的,所以要求在傳真上加上各自的簽名,也就是客戶端的IP地址了。既然要獲得計(jì)算機(jī)的IP地址,請(qǐng)一定要記得加上對(duì)DNS的命名空間引用:
using System.Net;

  因?yàn)槲覀儑?yán)格按照分布式處理程序的部署方式,所以在客戶端只需要添加公共程序集(Common.dll)的引用就可以了。而在服務(wù)端呢,則必須添加公共程序集和遠(yuǎn)程對(duì)象程序集兩者的引用。

OK,程序完成,我們來(lái)看看這個(gè)簡(jiǎn)陋的傳真機(jī):
客戶端:

嘿嘿,做夢(mèng)都想放假啊。好的,傳真寫好了,發(fā)送吧!再看看服務(wù)端,great,老板已經(jīng)收到我的請(qǐng)假條傳真了!

二、 客戶端訂閱服務(wù)端事件

  嘿嘿,吃甘蔗要先吃甜的一段,做事情我也喜歡先做容易的?,F(xiàn)在,好日子過(guò)去了,該吃點(diǎn)苦頭了。我們先回憶一下剛才的實(shí)現(xiàn)方法,再來(lái)思考怎么實(shí)現(xiàn)客戶端訂閱服務(wù)端事件?

  在前一節(jié),事件被放到遠(yuǎn)程對(duì)象中,客戶端激活對(duì)象后,就可以發(fā)送消息了。而在服務(wù)端,只需要訂閱該事件就可以?,F(xiàn)在思路應(yīng)該反過(guò)來(lái),由客戶端訂閱事件,服務(wù)端發(fā)送消息。就這么簡(jiǎn)單嗎?先不要高興得太早。我們想一想,發(fā)送消息的任務(wù)是誰(shuí)來(lái)完成的?是遠(yuǎn)程對(duì)象。而遠(yuǎn)程對(duì)象是什么時(shí)候創(chuàng)建的呢?我們仔細(xì)思考Remoting的幾種激活方式,不管是服務(wù)端激活,還是客戶端激活,他們的工作原理都是:客戶端決定了服務(wù)器創(chuàng)建遠(yuǎn)程對(duì)象實(shí)例的時(shí)機(jī),例如調(diào)用了遠(yuǎn)程對(duì)象的方法。而服務(wù)端所作的工作則是注冊(cè)該遠(yuǎn)程對(duì)象。

回憶這三種激活方式在服務(wù)端的代碼:

SingleCall激活方式:

RemotingConfiguration.RegisterWellKnownServiceType(
 typeof(BroadCastObj),"BroadCastMessage.soap",
 WellKnownObjectMode.Singlecall);

SingleTon激活方式:

RemotingConfiguration.RegisterWellKnownServiceType(
 typeof(BroadCastObj),"BroadCastMessage.soap",
 WellKnownObjectMode.Singleton);

客戶端激活方式:

RemotingConfiguration.ApplicationName = “BroadCastMessage.soap”
RemotingConfiguration.RegisterActivatedServiceType(typeof(BroadCastObj));

請(qǐng)注意Register這個(gè)詞語(yǔ),它表達(dá)的含義就是注冊(cè)。也就是說(shuō),在服務(wù)端并沒(méi)有顯示的創(chuàng)建遠(yuǎn)程對(duì)象實(shí)例。沒(méi)有該實(shí)例,又如何廣播消息呢?

或許有人會(huì)想,在注冊(cè)遠(yuǎn)程對(duì)象之后,顯式實(shí)例該對(duì)象不就可以了嗎?也就是說(shuō),在注冊(cè)后加上這一段代碼:

BroadCastObj obj = new BroadCastObj();

  然而,我們要明白一個(gè)事實(shí):就是服務(wù)端和客戶端是處于兩個(gè)不同的應(yīng)用程序域中。因此在Remoting中,客戶端獲得的遠(yuǎn)程對(duì)象實(shí)際是服務(wù)端注冊(cè)對(duì)象的代理。如果我們?cè)谧?cè)后,人工去創(chuàng)建一個(gè)實(shí)例,而非Remoting在激活后自動(dòng)創(chuàng)建的對(duì)象,那么客戶端獲得的對(duì)象與服務(wù)端人工創(chuàng)建的實(shí)例是兩個(gè)迥然不同的對(duì)象??蛻舳双@得的代理對(duì)象并沒(méi)有指向你剛才創(chuàng)建的obj實(shí)例。所以obj發(fā)送的消息,客戶端根本無(wú)法捕捉。

  那么,我們只有望洋興嘆,束手無(wú)策了嗎?別著急,別忘了在服務(wù)器注冊(cè)對(duì)象方法中,還有一種方法,即Marshal方法啊。還記得Marshal的實(shí)現(xiàn)方式嗎?

BroadCastObj Obj = new BroadCastObj();
ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");

  這個(gè)方法與前不一樣。前面的三種方式,遠(yuǎn)程對(duì)象是根據(jù)客戶端調(diào)用的方式,來(lái)自動(dòng)創(chuàng)建的。而Marshal方法呢?則顯式地創(chuàng)建了遠(yuǎn)程對(duì)象實(shí)例,然后將其Marshal到通道中,形成ObjRef指向?qū)ο蟮拇?。只要生命周期沒(méi)有結(jié)束,這個(gè)對(duì)象就一直存在。而此時(shí)客戶端獲得的對(duì)象,正是創(chuàng)建的Obj實(shí)例的代理。

OK,這個(gè)問(wèn)題解決了,我們來(lái)看看具體實(shí)現(xiàn)。
公共程序集和遠(yuǎn)程對(duì)象與前相似,就不再贅述,只附上代碼:
公共程序集:

public delegate void BroadCastEventHandler(string info);

public interface IBroadCast
{
 event BroadCastEventHandler BroadCastEvent;
 void BroadCastingInfo(string info);
}

遠(yuǎn)程對(duì)象類:

public event BroadCastEventHandler BroadCastEvent;

#region IBroadCast 成員

//[OneWay]
public void BroadCastingInfo(string info)
{
 if (BroadCastEvent != null)
 {
 BroadCastEvent(info);
 }
}

#endregion

public override object InitializeLifetimeService()
{
 return null;
}

  下面,該實(shí)現(xiàn)服務(wù)端了。在實(shí)現(xiàn)之前,我還想羅嗦幾句。在第一節(jié)中,我們實(shí)現(xiàn)了服務(wù)端訂閱客戶端事件。由于訂閱事件是在服務(wù)端發(fā)生的,因此事件本身并未被傳送。被序列化的僅僅是傳遞的消息,即Fax而已?,F(xiàn)在,方向發(fā)生了改變,傳送消息的是服務(wù)端,客戶端訂閱了事件。但這個(gè)事件是放在遠(yuǎn)程對(duì)象中的,因此事件必須被序列化。而在.Net Framework1.1中,微軟對(duì)序列化的安全級(jí)別進(jìn)行了限制。有關(guān)委托和事件的序列化、反序列化默認(rèn)是禁止的,所以我們應(yīng)該將TypeFilterLevel的屬性值設(shè)置為Full枚舉值。因此在服務(wù)端注冊(cè)通道的方式就發(fā)生了改變:

private void StartServer()
{
 BinaryServerFormatterSinkProvider serverProvider = new
 BinaryServerFormatterSinkProvider();
 BinaryClientFormatterSinkProvider clientProvider = new
 BinaryClientFormatterSinkProvider();
 serverProvider.TypeFilterLevel = TypeFilterLevel.Full;

 IDictionary props = new Hashtable();
 props["port"] = 8080;
 HttpChannel channel = new HttpChannel(props,clientProvider,serverProvider);
 ChannelServices.RegisterChannel(channel);

 Obj = new BroadCastObj();
 ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap"); 
}

注意語(yǔ)句serverProvider.TypeFilterLevel = TypeFilterLevel.Full;此語(yǔ)句即設(shè)置序列化安全級(jí)別的。要使用TypeFilterLevel屬性,必須申明命名空間:
using System.Runtime.Serialization.Formatters;

而后面兩條語(yǔ)句就是注冊(cè)遠(yuǎn)程對(duì)象。由于在我的廣播程序中,發(fā)送廣播消息是放在另一個(gè)窗口中,因此我將該遠(yuǎn)程對(duì)象聲明為公共靜態(tài)對(duì)象:

public static BroadCastObj Obj = null;

然后在調(diào)用窗口事件中加入:

private void ServerForm_Load(object sender, System.EventArgs e)
{
 StartServer();
 lbMonitor.Items.Add("Server started!");
}

來(lái)看看界面,首先啟動(dòng)服務(wù)端主窗口:

我放了一個(gè)ListBox控件來(lái)顯示一些信息,例如顯示服務(wù)器啟動(dòng)了。而BroadCast按鈕就是廣播消息的,單擊該按鈕,會(huì)彈出一個(gè)對(duì)話框:

BraodCast按鈕的代碼:

private void btnBC_Click(object sender, System.EventArgs e)
{ 
 BroadCastForm bcForm = new BroadCastForm();
 bcForm.StartPosition = FormStartPosition.CenterParent;
 bcForm.ShowDialog();
}

在對(duì)話框中,最主要的就是Send按鈕:

if (txtInfo.Text != string.Empty)
{ 
 ServerForm.Obj.BroadCastingInfo(txtInfo.Text);
}
else
{
 MessageBox.Show("請(qǐng)輸入信息!");
}


但是很簡(jiǎn)單,就是調(diào)用遠(yuǎn)程對(duì)象的發(fā)送消息方法而已。

現(xiàn)在該實(shí)現(xiàn)客戶端了。我們可以參照前面的例子,只是把服務(wù)端改為客戶端而已。另外考慮到序列化安全級(jí)別的問(wèn)題,所以代碼會(huì)是這樣:

private void ClientForm_Load(object sender, System.EventArgs e)
{
 BinaryServerFormatterSinkProvider serverProvider = new
 BinaryServerFormatterSinkProvider();
 BinaryClientFormatterSinkProvider clientProvider = new
 BinaryClientFormatterSinkProvider();
 serverProvider.TypeFilterLevel = TypeFilterLevel.Full;

 IDictionary props = new Hashtable();
 props["port"] = 0;
 HttpChannel channel = new HttpChannel(props,clientProvider,serverProvider);
 ChannelServices.RegisterChannel(channel);

 watch = (IBroadCast)Activator.GetObject(
 typeof(IBroadCast),"http://localhost:8080/BroadCastMessage.soap"); 
 watch.BroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);
}

注意客戶端通道的端口號(hào)應(yīng)設(shè)置為0,這表示客戶端自動(dòng)選擇可用的端口號(hào)。如果要設(shè)置為指定的端口號(hào),則必須保證與服務(wù)端通道的端口號(hào)不相同。
然后是,BroadCastEventHandler委托的方法:

public void BroadCastingMessage(string message)
{
 txtMessage.Text += "I got it:" + message; 
 txtMessage.Text += System.Environment.NewLine; 
}

客戶端界面如圖:

好,下面讓我們滿懷期盼,來(lái)運(yùn)行這段程序。首先啟動(dòng)服務(wù)端應(yīng)用程序,然后啟動(dòng)客戶端。哎呀,糟糕,居然出現(xiàn)了錯(cuò)誤信息!

  “人之不如意事,十常居八九?!辈挥镁趩?,讓我們分析原因。首先看看錯(cuò)誤信息,它報(bào)告我們沒(méi)有找到Client程序集。然而事實(shí)上,Client程序集當(dāng)然是有的。那么再來(lái)調(diào)試一下,是哪一步出現(xiàn)的問(wèn)題呢?設(shè)置好斷點(diǎn),進(jìn)行逐語(yǔ)句跟蹤。前面注冊(cè)通道一切正常,當(dāng)運(yùn)行到watch.BroadCastEvent += new BroadCastEventHandler(BroadCastingMessage)語(yǔ)句時(shí),錯(cuò)誤出現(xiàn)了!

  也就是說(shuō),遠(yuǎn)程對(duì)象的創(chuàng)建是成功的,但在訂閱事件的時(shí)候失敗了。原因是什么呢?原來(lái),客戶端的委托是通過(guò)序列化后獲得的,在訂閱事件的時(shí)候,委托試圖裝載包含與簽名相同的方法的程序集,也就是BroadCastingMessage方法所在的程序集Client。然而這個(gè)裝載的過(guò)程發(fā)生在服務(wù)端,而在服務(wù)端,并沒(méi)有Client程序集存在,自然就發(fā)生了上面的異常。

  原因清楚了,怎么解決?首先BroadCastingMessage方法肯定是在客戶端中,所以不可避免,委托裝載Client程序集的過(guò)程也必須在客戶端完成。而服務(wù)端事件又是由遠(yuǎn)程對(duì)象來(lái)捕獲的,因此,在客戶端注冊(cè)的也就必須是遠(yuǎn)程對(duì)象事件了。一個(gè)要求必須在客戶端,一個(gè)又要求必須在服務(wù)端,事情出現(xiàn)了自相矛盾的地方。

那么,讓我們先想想這樣一個(gè)例子。假設(shè)我們要交換x和y的值,該這樣完成?很簡(jiǎn)單,引入一個(gè)中間變量就可以了。

int x=1,y=2,z;
z = x;
x = y;
y = z;

這個(gè)游戲相信大家都會(huì)玩吧,那么好的,我們也需要引入這樣一個(gè)“中間”對(duì)象。這個(gè)中間對(duì)象和原來(lái)的遠(yuǎn)程對(duì)象在事件處理方面,代碼完全一致:

public class EventWrapper:MarshalByRefObject
{
 public event BroadCastEventHandler LocalBroadCastEvent;

 //[OneWay]
 public void BroadCasting(string message)
 {
 LocalBroadCastEvent(message);
 }

 public override object InitializeLifetimeService()
 {
 return null;
 }
}

不過(guò)不同之處在于:這個(gè)Wrapper類必須在客戶端和服務(wù)端上都要部署,所以,這個(gè)類應(yīng)該放在公共程序集Common.dll中。

現(xiàn)在再來(lái)修改原來(lái)的客戶端代碼:

watch = (IBroadCast)Activator.GetObject(
 typeof(IBroadCast),"http://localhost:8080/BroadCastMessage.soap"); 
watch.BroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);

修改為:

watch = (IBroadCast)Activator.GetObject(
 typeof(IBroadCast),"http://localhost:8080/BroadCastMessage.soap");
EventWrapper wrapper = new EventWrapper(); 
wrapper.LocalBroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);
watch.BroadCastEvent += new BroadCastEventHandler(wrapper.BroadCasting);

為什么這樣做就可以了呢?也許畫一幅圖就很容易說(shuō)明,可惜我的藝術(shù)天分實(shí)在很糟糕,我希望以后可以改進(jìn)這一點(diǎn)。還是用文字來(lái)說(shuō)明吧。

前面說(shuō),委托要裝載client程序集?,F(xiàn)在我們把遠(yuǎn)程對(duì)象委托裝載的權(quán)利移交給EventWrapper。因?yàn)檫@個(gè)類對(duì)象是放在客戶端的,所以它要裝載client程序集絲毫沒(méi)有問(wèn)題。語(yǔ)句:

EventWrapper wrapper = new EventWrapper(); 
wrapper.LocalBroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);

實(shí)現(xiàn)了這個(gè)功能。

  不過(guò)此時(shí)雖然訂閱了事件,但事件還是客戶端的,沒(méi)有與服務(wù)端聯(lián)系起來(lái)。而服務(wù)端的事件是放到遠(yuǎn)程對(duì)象中的,所以,還要訂閱事件,這個(gè)任務(wù)由遠(yuǎn)程對(duì)象watch來(lái)完成。但此時(shí)它訂閱的不再是BroadCastingMessage了,而是EventWrapper的觸發(fā)事件方法BroadCasting。那么此時(shí)委托同樣要裝載程序集,但此時(shí)裝載的就是BroadCasting所在的程序集了。由于裝載發(fā)生的地點(diǎn)是在服務(wù)端。呵呵,高興的是,BroadCasting所在的程序集正是公共程序集(前面已說(shuō)過(guò),EventWrapper應(yīng)放到公共程序集Common.dll中),而公共程序集在服務(wù)端和客戶端都已經(jīng)部署了。自然就不會(huì)出現(xiàn)找不到程序集的問(wèn)題了。

注意:EventWrapper因?yàn)橐貙慖nitializeLifetimeService()方法,所以仍然要繼承MarshalByRefObject類。

現(xiàn)在再來(lái)運(yùn)行程序。首先運(yùn)行服務(wù)端;然后運(yùn)行客戶端,OK,客戶端窗體出現(xiàn)了:

然后我們?cè)诜?wù)端單擊“BroadCast”按鈕,發(fā)送廣播消息:

單擊“Send”發(fā)送,再來(lái)看看客戶端,會(huì)是怎樣?Fine,I got it!

怎么樣,很酷吧!你也可以同時(shí)打開多個(gè)客戶端,它們都將收到這個(gè)廣播信息。如果你覺得這個(gè)廣播聲音太吵,那就請(qǐng)你在客戶端取消廣播吧。在Cancle按鈕中:

private void btnCancle_Click(object sender, System.EventArgs e)
{
 watch.BroadCastEvent -= new BroadCastEventHandler(wrapper.BroadCasting);
 MessageBox.Show("取消訂閱廣播成功!");
}

當(dāng)然這個(gè)時(shí)候wrapper對(duì)象應(yīng)該被申明為private對(duì)象了:
private EventWrapper wrapper = null;

取消后,你試著再?gòu)V播一下,恭喜你,你不會(huì)聽到噪音了!

三、 客戶端訂閱客戶端事件

  有了前面的基礎(chǔ),再來(lái)看客戶端訂閱客戶端事件,就簡(jiǎn)單多了。而本文寫到這里,我也很累了,你也被我啰嗦得不耐煩了。你心里在喊,“饒了我吧!”其實(shí),我又何嘗不是如此。所以我只提供一個(gè)思路,有興趣的朋友,可以自己寫一個(gè)程序。

  其實(shí)方法很簡(jiǎn)單,和第二種情況類似。發(fā)送信息的客戶端,只需要獲得遠(yuǎn)程對(duì)象后,發(fā)送消息就可以了。而接收信息的客戶端,負(fù)責(zé)訂閱該事件。由于事件都是放到遠(yuǎn)程對(duì)象中,因此訂閱的方法和第二種情況沒(méi)有什么區(qū)別!

  特殊的情況是,我們可以用第三種情況來(lái)代替第二種。只要你把發(fā)送信息的客戶端放到服務(wù)端就可以了。當(dāng)然需要做一些額外的工作,有興趣的朋友可以去實(shí)現(xiàn)一下。在我的示例程序中,已經(jīng)用這種方法模擬實(shí)現(xiàn)了服務(wù)端的廣播,大家可以去看看。

四、 一點(diǎn)補(bǔ)充

我在前面的事件處理中,使用的都是默認(rèn)的EventArgs。如果要定義自己的EventArgs,就不相同了。因?yàn)樵撔畔⑹莻髦敌蛄谢虼吮仨毤由蟍Serializable],且必須放到公共程序集中,部署到服務(wù)端和客戶端。例如:

[Serializable]
public class BroadcastEventArgs:EventArgs
{
 private string msg = null;
 public BroadcastEventArgs(string message)
 {
 msg = message;
 }

 public string Message
 {
 get {return msg;}
 }
}

五、持續(xù)改進(jìn)(經(jīng)Beta的提醒,我改進(jìn)了我的程序,并對(duì)文章進(jìn)行了修改 2004年12月13日)

  也許,細(xì)心的讀者注意到了,在我的遠(yuǎn)程對(duì)象類和EventWrapper類中,觸發(fā)事件方法的Attribute[OneWay]被我注釋掉了。我看到很多資料上寫到,在Remoting中處理事件,觸發(fā)事件的方法必須具有這個(gè)Attribute。這個(gè)attribute究竟有什么用?

  在發(fā)送事件消息的時(shí)候,事件的訂閱者會(huì)觸發(fā)事件,然后響應(yīng)該事件。然而當(dāng)事件的訂閱者發(fā)生錯(cuò)誤的時(shí)候呢?例如,發(fā)送事件消息的時(shí)候,才發(fā)現(xiàn)根本沒(méi)有事件訂閱者;或者事件的訂閱者出現(xiàn)故障,如斷電、或異常關(guān)機(jī)。此時(shí),發(fā)送事件一方會(huì)因?yàn)檎也坏秸_的事件訂閱者,而發(fā)生異常。以我的程序?yàn)槔.?dāng)我們分別打開服務(wù)端和客戶端程序的時(shí)候,此時(shí)廣播信息正常。然而,當(dāng)我們關(guān)閉客戶端后,由于該客戶端沒(méi)有取消訂閱,此時(shí)異常發(fā)生,提示信息如圖:

 ?。ú恢罏槭裁?,這個(gè)異常與客戶端連接服務(wù)端出現(xiàn)的異常一樣。這個(gè)異常容易讓人產(chǎn)生誤會(huì)。)

  如果這個(gè)時(shí)候我們同時(shí)打開了多個(gè)客戶端,那么其他客戶端就會(huì)因?yàn)檫@一個(gè)客戶端關(guān)閉造成的錯(cuò)誤,而無(wú)法收到廣播信息。那么讓我們先做第一步改進(jìn):

1、先考慮正常情況。在我的客戶端,雖然提供了取消訂閱的操作,但并沒(méi)有考慮用戶關(guān)閉客戶端的情況。即,關(guān)閉客戶端時(shí),并未取消事件的訂閱,所以我們應(yīng)該在關(guān)閉客戶端窗體中寫入:

private void ClientForm_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
 watch.BroadCastEvent -= new BroadCastEventHandler(wrapper.BroadCasting);
}

2、僅僅是這樣還不夠。如果客戶端并沒(méi)有正常關(guān)閉,而是因?yàn)橥蝗粩嚯姸鴮?dǎo)致客戶端關(guān)閉呢?此時(shí),客戶端還沒(méi)有來(lái)得及取消事件訂閱呢。在這種情況下,我們需要用到OneWayAttribute。

前面說(shuō)到,發(fā)送事件一方如果找不到正確的事件訂閱者,會(huì)發(fā)生異常。也就是說(shuō),這個(gè)事件是unreachable的。幸運(yùn)的是,OneWayAttribute恰好解決了這個(gè)問(wèn)題。其實(shí)從該特性的命名OneWay,大約也能猜到其中的含義。當(dāng)事件不可到達(dá),無(wú)法發(fā)送時(shí),正常情況下,會(huì)返回一個(gè)異常信息。如果加上OneWayAttribute,這個(gè)事件的發(fā)送就變成單向的了。假如此時(shí)發(fā)生異常,那么系統(tǒng)會(huì)自動(dòng)拋掉該異常信息。由于沒(méi)有異常信息的返回,發(fā)送信息方會(huì)認(rèn)為發(fā)送信息成功了。程序會(huì)正常運(yùn)行,錯(cuò)誤的客戶端被忽略,而正確的客戶端仍然能夠收到廣播信息。

因此,遠(yuǎn)程對(duì)象的代碼就應(yīng)該是這樣:

public event BroadCastEventHandler BroadCastEvent;

IBroadCast 成員

public override object InitializeLifetimeService()
{
 return null;
}

3、最后的改進(jìn)

  使用OneWay固然可以解決上述的問(wèn)題,但不夠友好。因?yàn)閷?duì)于廣播消息的一方來(lái)說(shuō),象被蒙上了眼睛一樣,對(duì)于客戶端發(fā)生的事情懵然不知。這并不是一個(gè)好的idea。在Ingo Rammer的Advanced .NET Remoting一書中,Ingo Rammer先生提出了一個(gè)更好的辦法,就是在發(fā)送信息一方時(shí),檢查了委托鏈。并在委托鏈的遍歷中來(lái)捕獲異常。當(dāng)其中一個(gè)委托發(fā)生異常時(shí),顯示提示信息。然后繼續(xù)遍歷后面的委托,這樣既保證了異常信息的提示,又保證了其他訂閱者正常接收消息。因此,我對(duì)本例的遠(yuǎn)程對(duì)象進(jìn)行了修改,注釋掉[OneWay],修改了BroadCastInfo()方法:

//[OneWay]
public void BroadCastingInfo(string info)
{
 if (BroadCastEvent != null)
 {
 BroadCastEventHandler tempEvent = null;

 int index = 1; //記錄事件訂閱者委托的索引,為方便標(biāo)識(shí),從1開始。
 foreach (Delegate del in BroadCastEvent.GetInvocationList())
 {
 try
 {
 tempEvent = (BroadCastEventHandler)del;
 tempEvent(info);
 }
 catch
 { 
 MessageBox.Show("事件訂閱者" + index.ToString() + "發(fā)生錯(cuò)誤,系統(tǒng)將取消事件訂閱!");
 BroadCastEvent -= tempEvent;
 }
 index++;
 } 
 }
 else
 {
 MessageBox.Show("事件未被訂閱或訂閱發(fā)生錯(cuò)誤!");
 }
}

我們來(lái)試驗(yàn)一下。首先打開服務(wù)端,然后同時(shí)打開三個(gè)客戶端。廣播消息:

消息發(fā)送正常。

接著關(guān)閉其中一個(gè)客戶端窗口,再?gòu)V播消息(注意為模擬客戶端異常情況,應(yīng)在ClientForm_Closing方法中把第一步改進(jìn)的取消訂閱代碼注釋。否則不會(huì)發(fā)生異常。難道你真的愿意用斷電來(lái)導(dǎo)致異常發(fā)生嗎^_^),結(jié)果如圖:

此時(shí)服務(wù)端報(bào)告了“事件訂閱者1發(fā)生錯(cuò)誤,系統(tǒng)將取消事件訂閱”。注意此時(shí)另外兩個(gè)客戶端,還是和前面一樣,只有兩條廣播信息。

當(dāng)我們點(diǎn)擊提示框的“確定”按鈕后,廣播仍然發(fā)送:

通過(guò)這樣的改進(jìn)后,程序更加的完善,也更加的健壯和友好!

附:
示例代碼說(shuō)明:
1、Remoting事件(客戶端發(fā)傳真)壓縮包:為第一節(jié)內(nèi)容;
2、Remoting事件(服務(wù)端廣播)壓縮包:為第二節(jié)、第三節(jié)內(nèi)容,其中:
第二節(jié)代碼包含于:
#region 客戶端訂閱服務(wù)端事件
#endregion
第三節(jié)代碼包含于:
#region 客戶端訂閱客戶端事件
#endregion
如果要實(shí)現(xiàn)第二節(jié)的程序,請(qǐng)注釋掉第三節(jié)代碼;反之亦然。示例程序默認(rèn)為第二節(jié)程序。
3、運(yùn)行示例程序時(shí),請(qǐng)先運(yùn)行服務(wù)端程序,然后運(yùn)行客戶端程序。否則會(huì)拋出“基礎(chǔ)連接已關(guān)閉”的異常。
4、解決方案均放在Common(或ICommon)文件夾中。

5、改進(jìn)后的代碼放到Remoting事件(服務(wù)端廣播改進(jìn))壓縮包中,大家可以比較一下改進(jìn)后的程序有何不同!

以上就是.Net Remoting中Remoting事件處理的全部?jī)?nèi)容,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • Microsoft .Net Remoting系列教程之二:Marshal、Disconnect與生命周期以及跟蹤服務(wù)
  • Microsoft .Net Remoting系列教程之一:.Net Remoting基礎(chǔ)篇
  • Flex 錯(cuò)誤(mx.messaging.messages::RemotingMessage)分析
  • ASP.NET通過(guò)Remoting service上傳文件
  • java webservice上傳下載文件代碼分享
  • java通過(guò)客戶端訪問(wèn)服務(wù)器webservice的方法
  • ASP.NET使用WebService實(shí)現(xiàn)天氣預(yù)報(bào)功能
  • jQuery調(diào)用Webservice傳遞json數(shù)組的方法
  • C# WebService發(fā)布以及IIS發(fā)布
  • Remoting和Webservice的詳細(xì)介紹及區(qū)別

標(biāo)簽:慶陽(yáng) 甘肅 海西 中衛(wèi) 清遠(yuǎn) 臨夏 聊城

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Microsoft .Net Remoting系列教程之三:Remoting事件處理全接觸》,本文關(guān)鍵詞  Microsoft,.Net,Remoting,系列,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Microsoft .Net Remoting系列教程之三:Remoting事件處理全接觸》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于Microsoft .Net Remoting系列教程之三:Remoting事件處理全接觸的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    临沂市| 晴隆县| 宝丰县| 襄城县| 齐河县| 永安市| 辽宁省| 丰城市| 普安县| 吉林省| 江油市| 育儿| 祁东县| 德钦县| 游戏| 阿克| 左权县| 平南县| 砚山县| 揭西县| 兴安县| 台山市| 兖州市| 南阳市| 吉林市| 博客| 察哈| 神木县| 红安县| 依兰县| 拉萨市| 柳州市| 喀喇沁旗| 雅江县| 如东县| 紫阳县| 龙泉市| 鹿邑县| 德钦县| 靖宇县| 苍南县|