C# NetRemoting實現(xiàn)雙向通信
閑來無事想玩玩雙向通信,實現(xiàn)類似QQ的互發(fā)消息的功能。于是乎開始學(xué)習(xí).Net Remoting.
.Net Remoting 是由客戶端通過Remoting,訪問通道以獲得服務(wù)端對象,再通過代理解析為客戶端對象來實現(xiàn)通信的。也就是說對象是由服務(wù)端創(chuàng)建的。
先上代碼
首先是ICommand庫
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ICommand { public interface IRemotingObject { event SendHandler ClientToServer; event ReceiveHandler ServerToClient; event UserChangedHandler Login; event UserChangedHandler Exit; /// <summary> /// 加法運算 /// </summary> /// <param name="x1">參數(shù)1</param> /// <param name="x2">參數(shù)2</param> /// <returns></returns> string SUM(int x1, int x2); /// <summary> /// 獲取服務(wù)端事件列表 /// </summary> Delegate[] GetServerEventList(); /// <summary> /// 發(fā)送消息 /// </summary> /// <param name="info"></param> /// <param name="toName"></param> void ToServer(object info, string toName); /// <summary> /// 接受信息 /// </summary> /// <param name="info"></param> /// <param name="toName"></param> void ToClient(object info, string toName); void ToLogin(string name); void ToExit(string name); } /// <summary> /// 客戶端發(fā)送消息 /// </summary> /// <param name="info">信息</param> /// <param name="toName">發(fā)送給誰,""表示所有人,null表示沒有接收服務(wù)器自己接收,其他表示指定某人</param> public delegate void SendHandler(object info, string toName); /// <summary> /// 客戶端接收消息 /// </summary> /// <param name="info">信息</param> /// <param name="toName">發(fā)送給誰,""表示所有人,null表示沒有接收服務(wù)器自己接收,其他表示指定某人</param> public delegate void ReceiveHandler(object info, string toName); /// <summary> /// 用戶信息事件 /// </summary> /// <param name="name">用戶名</param> public delegate void UserChangedHandler(string name); }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ICommand { public class SwapObject : MarshalByRefObject { public event ReceiveHandler SwapServerToClient { add { _receive += value; } remove { _receive -= value; } } /// <summary> /// 接受信息 /// </summary> /// <param name="info"></param> /// <param name="toName"></param> public void ToClient(object info, string toName) { if (_receive != null) _receive(info, toName); } //無限生命周期 public override object InitializeLifetimeService() { return null; } private ReceiveHandler _receive; } }
第一個類就是定義一些接口,和一些委托,沒有實質(zhì)性的東西。
第二個類是定義了上一個接口類中的ToClient的事件和方法,作用之后會講到。
然后就是集成ICommand接口的實質(zhì)性的數(shù)據(jù)類
using System; using System.Collections.Generic; using System.Linq; using System.Text; using ICommand; namespace NetRemoting { public class RemotingObject : MarshalByRefObject, IRemotingObject { /// <summary> /// 發(fā)送事件 /// </summary> public event SendHandler ClientToServer { add { _send += value; } remove { _send -= value; } } /// <summary> /// 接收消息事件 /// </summary> public event ReceiveHandler ServerToClient; /// <summary> /// 發(fā)送事件 /// </summary> public event UserChangedHandler Login { add { _login += value; } remove { _login -= value; } } /// <summary> /// 發(fā)送事件 /// </summary> public event UserChangedHandler Exit { add { _exit += value; } remove { _exit -= value; } } /// <summary> /// 加法運算 /// </summary> /// <param name="x1">參數(shù)1</param> /// <param name="x2">參數(shù)2</param> /// <returns></returns> public string SUM(int x1, int x2) { return x1 + "+" + x2 + "=" + (x1 + x2); } /// <summary> /// 綁定服務(wù)端向客戶端發(fā)送消息的事件方法 /// </summary> /// <param name="receive">接收事件</param> public Delegate[] GetServerEventList() { return this.ServerToClient.GetInvocationList(); } /// <summary> /// 發(fā)送消息 /// </summary> /// <param name="info"></param> /// <param name="toName"></param> public void ToServer(object info, string toName) { if (_send != null) _send(info, toName); } /// <summary> /// 接收消息 /// </summary> /// <param name="info"></param> /// <param name="toName"></param> public void ToClient(object info, string toName) { if (_receive != null) _receive(info, toName); } /// <summary> /// 登錄 /// </summary> /// <param name="name">用戶名</param> public void ToLogin(string name) { if (!_nameHash.Contains(name)) { _nameHash.Add(name); if (_login != null) _login(name); } else { throw new Exception("用戶已存在"); } } /// <summary> /// 退出 /// </summary> /// <param name="name">用戶名</param> public void ToExit(string name) { if (_nameHash.Contains(name)) { _nameHash.Remove(name); if (_exit != null) _exit(name); } } private SendHandler _send; private ReceiveHandler _receive; private UserChangedHandler _login; private UserChangedHandler _exit; private HashSet<string> _nameHash = new HashSet<string>(); } }
該類集成了MarshalByRefObject
由于Remoting傳遞的對象是以引用的方式,因此所傳遞的遠(yuǎn)程對象類必須繼承MarshalByRefObject。MSDN對MarshalByRefObject的說明是:MarshalByRefObject 是那些通過使用代理交換消息來跨越應(yīng)用程序域邊界進(jìn)行通信的對象的基類。不是從 MarshalByRefObject 繼承的對象會以隱式方式按值封送。當(dāng)遠(yuǎn)程應(yīng)用程序引用一個按值封送的對象時,將跨越遠(yuǎn)程處理邊界傳遞該對象的副本。因為您希望使用代理方法而不是副本方法進(jìn)行通信,因此需要繼承MarshallByRefObject。
該類主要是定義了一些方法用于客戶端觸發(fā)事件,ToServer,ToClient,ToLogin,ToExit以及一些事件,客戶端發(fā)向服務(wù)端的事件,和服務(wù)端發(fā)向客戶端的事件。
_nameHash 只是記錄有哪些用戶登錄了。
接下去就是客戶端和服務(wù)端了。
首先服務(wù)端:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using NetRemoting; using System.Collections; using System.Runtime.Serialization.Formatters; using ICommand; namespace NetRemotingServer { public partial class Server : Form { public Server() { InitializeComponent(); Initialize(); } /// <summary> /// 注冊通道 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Server_Load(object sender, EventArgs e) { ChannelServices.RegisterChannel(_channel, false); //RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject), "SumMessage", WellKnownObjectMode.Singleton); //a方案 /*將給定的 System.MarshalByRefObject 轉(zhuǎn)換為具有指定 URI 的 System.Runtime.Remoting.ObjRef 類的實例。 ObjRef :存儲生成代理以與遠(yuǎn)程對象通信所需要的所有信息。*/ ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage");//b方案 _remotingObject.ClientToServer += (info, toName) => { rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(info.ToString() + "\r\n"); })); SendToClient(info, toName); }; _remotingObject.Login += (name) => { rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(name + " 登錄" + "\r\n"); })); }; _remotingObject.Exit += (name) => { rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(name + " 退出" + "\r\n"); })); }; } /// <summary> /// 注銷通道 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Server_FormClosing(object sender, FormClosingEventArgs e) { if (_channel != null) { _channel.StopListening(null); ChannelServices.UnregisterChannel(_channel); } } /// <summary> /// 廣播消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { SendToClient(txtSend.Text, txtName.Text); } /// <summary> /// 發(fā)送消息到客戶端 /// </summary> /// <param name="info"></param> /// <param name="toName"></param> private void SendToClient(object info, string toName) { //foreach (var v in _remotingObject.GetServerEventList()) //{ // try // { // ReceiveHandler receive = (ReceiveHandler)v; // receive.BeginInvoke(info, toName, null, null); // } // catch // { } // } _remotingObject.ToClient(txtSend.Text, txtName.Text); } /// <summary> /// 初始化 /// </summary> private void Initialize() { //設(shè)置反序列化級別 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有類型的反序列化,級別很高 IDictionary idic = new Dictionary<string, string>(); idic["name"] = "serverHttp"; idic["port"] = "8022"; _channel = new HttpChannel(idic, clientProvider, serverProvider); _remotingObject = new RemotingObject(); } HttpChannel _channel; private RemotingObject _remotingObject; } }
然后客戶端:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using ICommand; using System.Runtime.Serialization.Formatters; using System.Collections; namespace NetRemotingClient { public partial class Client : Form { public Client() { InitializeComponent(); } /// <summary> /// 注冊通道 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Client_Load(object sender, EventArgs e) { try { //設(shè)置反序列化級別 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有類型的反序列化,級別很高 //信道端口 IDictionary idic = new Dictionary<string, string>(); idic["name"] = "clientHttp"; idic["port"] = "0"; HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider); ChannelServices.RegisterChannel(channel, false); _remotingObject = (IRemotingObject)Activator.GetObject(typeof(IRemotingObject), "http://localhost:8022/SumMessage"); //_remotingObject.ServerToClient += (info, toName) => { rtxMessage.AppendText(info + "\r\n"); }; SwapObject swap = new SwapObject(); _remotingObject.ServerToClient += swap.ToClient; swap.SwapServerToClient += (info, toName) => { rtxMessage.Invoke((MethodInvoker)(() => { if (toName == txtLogin.Text || toName == "") rtxMessage.AppendText(info + "\r\n"); })); }; } catch (Exception ex) { MessageBox.Show(ex.Message); } } /// <summary> /// 登錄 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnLogin_Click(object sender, EventArgs e) { try { if (txtLogin.Text == "") throw new Exception("用戶名不得為空"); _remotingObject.ToLogin(txtLogin.Text); } catch (Exception ex) { MessageBox.Show(ex.Message); } } /// <summary> /// 退出 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Client_FormClosing(object sender, FormClosingEventArgs e) { try { _remotingObject.ToExit(txtLogin.Text); } catch { } } /// <summary> /// 發(fā)送 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { //rtxMessage.AppendText(_remotingObject.SUM(2, 4) + "\r\n"); _remotingObject.ToServer(txtSend.Text, txtName.Text); } private IRemotingObject _remotingObject; } }
服務(wù)端實現(xiàn)步驟:
1、注冊通道
要跨越應(yīng)用程序域進(jìn)行通信,必須實現(xiàn)通道。如前所述,Remoting提供了IChannel接口,分別包含TcpChannel和HttpChannel兩種類型的通道。這兩種類型除了性能和序列化數(shù)據(jù)的格式不同外,實現(xiàn)的方式完全一致,因此下面我們就以TcpChannel為例。
注冊TcpChannel,首先要在項目中添加引用“System.Runtime.Remoting”,然后using名字空間:System.Runtime.Remoting.Channel.Tcp。代碼如下:
TcpChannel channel = new TcpChannel(8022); ChannelServices.RegisterChannel(channel);
在實例化通道對象時,將端口號作為參數(shù)傳遞。然后再調(diào)用靜態(tài)方法RegisterChannel()來注冊該通道對象即可。
2、注冊遠(yuǎn)程對象
注冊了通道后,要能激活遠(yuǎn)程對象,必須在通道中注冊該對象。根據(jù)激活模式的不同,注冊對象的方法也不同。
(1) SingleTon模式
對于WellKnown對象,可以通過靜態(tài)方法RemotingConfiguration.RegisterWellKnownServiceType()來實現(xiàn):
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleTon);
(2)SingleCall模式
注冊對象的方法基本上和SingleTon模式相同,只需要將枚舉參數(shù)WellKnownObjectMode改為SingleCall就可以了。
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleCall);
客戶端實現(xiàn)步驟:
1、注冊通道:
TcpChannel channel = new TcpChannel(); ChannelServices.RegisterChannel(channel);
注意在客戶端實例化通道時,是調(diào)用的默認(rèn)構(gòu)造函數(shù),即沒有傳遞端口號。事實上,這個端口號是缺一不可的,只不過它的指定被放在后面作為了Uri的一部分。
2、獲得遠(yuǎn)程對象。
與服務(wù)器端相同,不同的激活模式?jīng)Q定了客戶端的實現(xiàn)方式也將不同。不過這個區(qū)別僅僅是WellKnown激活模式和客戶端激活模式之間的區(qū)別,而對于SingleTon和SingleCall模式,客戶端的實現(xiàn)完全相同。
(1) WellKnown激活模式
要獲得服務(wù)器端的知名遠(yuǎn)程對象,可通過Activator進(jìn)程的GetObject()方法來獲得:
ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject( typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");
首先以WellKnown模式激活,客戶端獲得對象的方法是使用GetObject()。其中參數(shù)第一個是遠(yuǎn)程對象的類型。第二個參數(shù)就是服務(wù)器端的uri。如果是http通道,自然是用http://localhost:8022/ServiceMessage了。因為我是用本地機,所以這里是localhost,你可以用具體的服務(wù)器IP地址來代替它。端口必須和服務(wù)器端的端口一致。后面則是服務(wù)器定義的遠(yuǎn)程對象服務(wù)名,即ApplicationName屬性的內(nèi)容。
//設(shè)置反序列化級別 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有類型的反序列化,級別很高 //信道端口 IDictionary idic = new Dictionary<string, string>(); idic["name"] = "clientHttp"; idic["port"] = "0"; HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider);
從上述代碼中可以看到注冊方式有所變化,那是因為客戶端注冊服務(wù)端的事件時會報錯“不允許類型反序列化”。
還有一個需要注意的是:
ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage"); //RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject), "SumMessage", WellKnownObjectMode.Singleton); //調(diào)用系統(tǒng)自動創(chuàng)建,導(dǎo)致拿不到_remotingObject對象的實例化,這樣后期綁定事件就無法操作下去了,當(dāng)然也可以直接靜態(tài)事件綁定,這樣就不需要手動實例化對象了
通過該方法手動創(chuàng)建_remotingObject這個對象的實例化。
然后之前講到了一個SwapObject這個類,這個類的作用是事件交換。
_remotingObject.ServerToClient +=方法(); //這樣因為這個方法是客戶端的,服務(wù)端無法調(diào)用,所以需要一個中間轉(zhuǎn)換的 SwapObject swap = new SwapObject();//先創(chuàng)建一個Swap對象 _remotingObject.ServerToClient += swap.ToClient;//然后服務(wù)端事件發(fā)信息給swap,然后swap再通過事件發(fā)消息給客戶端,swap是客戶端創(chuàng)建的所以可以發(fā)送,而swap是服務(wù)端的類,所以服務(wù)端也能識別,swap起到了中間過渡的作用 swap.SwapServerToClient +=方法();
以上是兩天.Net Remoting的學(xué)習(xí)結(jié)果。
最后附上源碼:NetRemoting_jb51.rar
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#中Monitor對象與Lock關(guān)鍵字的區(qū)別分析
這篇文章主要介紹了C#中Monitor對象與Lock關(guān)鍵字的區(qū)別,需要的朋友可以參考下2013-06-06