本篇文章主要介紹了C# NetRemoting實現雙向通訊,.Net Remoting 是由用戶端通過Remoting,訪問通道以獲得服務端對象,再通過代理解析為用戶端對象來實現通訊的
閑來無事想玩玩雙向通訊,實作類別似QQ的互發訊息的功能。於是乎開始學習.Net Remoting.
.Net Remoting 是由用戶端通過Remoting,訪問通道以獲得服務端對象,再通過代理解析為用戶端對象來實現通訊的。也就是說對象是由服務端建立的。
先上代碼
首先是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">參數1</param> /// <param name="x2">參數2</param> /// <returns></returns> string SUM(int x1, int x2); /// <summary> /// 擷取服務端事件列表 /// </summary> Delegate[] GetServerEventList(); /// <summary> /// 發送訊息 /// </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> /// 用戶端發送訊息 /// </summary> /// <param name="info">資訊</param> /// <param name="toName">發送給誰,""表示所有人,null表示沒有接收伺服器自己接收,其他表示指定某人</param> public delegate void SendHandler(object info, string toName); /// <summary> /// 用戶端接收訊息 /// </summary> /// <param name="info">資訊</param> /// <param name="toName">發送給誰,""表示所有人,null表示沒有接收伺服器自己接收,其他表示指定某人</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; } }
第一個類就是定義一些介面,和一些委託,沒有實質性的東西。
第二個類是定義了上一個介面類中的ToClient的事件和方法,作用之後會講到。
然後就是整合ICommand介面的實質性的資料類
using System;using System.Collections.Generic;using System.Linq;using System.Text;using ICommand;namespace NetRemoting{ public class RemotingObject : MarshalByRefObject, IRemotingObject { /// <summary> /// 發送事件 /// </summary> public event SendHandler ClientToServer { add { _send += value; } remove { _send -= value; } } /// <summary> /// 接收訊息事件 /// </summary> public event ReceiveHandler ServerToClient; /// <summary> /// 發送事件 /// </summary> public event UserChangedHandler Login { add { _login += value; } remove { _login -= value; } } /// <summary> /// 發送事件 /// </summary> public event UserChangedHandler Exit { add { _exit += value; } remove { _exit -= value; } } /// <summary> /// 加法運算 /// </summary> /// <param name="x1">參數1</param> /// <param name="x2">參數2</param> /// <returns></returns> public string SUM(int x1, int x2) { return x1 + "+" + x2 + "=" + (x1 + x2); } /// <summary> /// 綁定服務端向用戶端發送訊息的事件方法 /// </summary> /// <param name="receive">接收事件</param> public Delegate[] GetServerEventList() { return this.ServerToClient.GetInvocationList(); } /// <summary> /// 發送訊息 /// </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傳遞的對象是以引用的方式,因此所傳遞的遠程對象類必須繼承MarshalByRefObject。MSDN對MarshalByRefObject的說明是:MarshalByRefObject 是那些通過使用代理交換訊息來跨越應用程式定義域邊界進行通訊的對象的基類。不是從 MarshalByRefObject 繼承的對象會以隱式方式按值封送。當遠程應用程式引用一個按值封送的對象時,將跨越遠端邊界傳遞該對象的副本。因為您希望使用代理方法而不是副本方法進行通訊,因此需要繼承MarshallByRefObject。
該類主要是定義了一些方法用於用戶端觸發事件,ToServer,ToClient,ToLogin,ToExit以及一些事件,用戶端發向服務端的事件,和服務端發向用戶端的事件。
_nameHash 只是記錄有哪些使用者登入了。
接下去就是用戶端和服務端了。
首先服務端:
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 轉換為具有指定 URI 的 System.Runtime.Remoting.ObjRef 類的執行個體。 ObjRef :儲存組建代理程式以與遠程對象通訊所需要的所有資訊。*/ 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> /// 發送訊息到用戶端 /// </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() { //設定還原序列化層級 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 { //設定還原序列化層級 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> /// 發送 /// </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; }}
服務端實現步驟:
1、註冊通道
要跨越應用程式定義域進行通訊,必須實現通道。如前所述,Remoting提供了IChannel介面,分別包含TcpChannel和HttpChannel兩種類型的通道。這兩種類型除了效能和序列化資料的格式不同外,實現的方式完全一致,因此下面我們就以TcpChannel為例。
註冊TcpChannel,首先要在項目中添加引用“System.Runtime.Remoting”,然後using名字空間:System.Runtime.Remoting.Channel.Tcp。代碼如下:
TcpChannel channel = new TcpChannel(8022);ChannelServices.RegisterChannel(channel);
在執行個體化通道對象時,將連接埠號碼作為參數傳遞。然後再調用靜態方法RegisterChannel()來註冊該通道對象即可。
2、註冊遠程對象
註冊了通道後,要能啟用遠程對象,必須在通道中註冊該對象。根據啟用模式的不同,註冊對象的方法也不同。
(1) SingleTon模式
對於WellKnown對象,可以通過靜態方法RemotingConfiguration.RegisterWellKnownServiceType()來實現:
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleTon);
(2)SingleCall模式
註冊對象的方法基本上和SingleTon模式相同,只需要將枚舉參數WellKnownObjectMode改為SingleCall就可以了。
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleCall);
用戶端實現步驟:
1、註冊通道:
TcpChannel channel = new TcpChannel();ChannelServices.RegisterChannel(channel);
注意在用戶端執行個體化通道時,是調用的預設建構函式,即沒有傳遞連接埠號碼。事實上,這個連接埠號碼是缺一不可的,只不過它的指定被放在後面作為了Uri的一部分。
2、獲得遠程對象。
與伺服器端相同,不同的啟用模式決定了用戶端的實現方式也將不同。不過這個區別僅僅是WellKnown啟用模式和用戶端啟用模式之間的區別,而對於SingleTon和SingleCall模式,用戶端的實現完全相同。
(1) WellKnown啟用模式
要獲得伺服器端的知名遠程對象,可通過Activator進程的GetObject()方法來獲得:
ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject( typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");
首先以WellKnown模式啟用,用戶端獲得對象的方法是使用GetObject()。其中參數第一個是遠程對象的類型。第二個參數就是伺服器端的uri。如果是http通道,自然是用localhost:8022/ServiceMessage了。因為我是用本地機,所以這裡是localhost,你可以用具體的伺服器IP地址來代替它。連接埠必須和伺服器端的連接埠一致。後面則是伺服器定義的遠程物件服務名,即ApplicationName屬性的內容。
//設定還原序列化層級 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);
從上述代碼中可以看到註冊方式有所變化,那是因為用戶端註冊服務端的事件時會報錯“不允許類型還原序列化”。
還有一個需要注意的是:
ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage");//RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject), "SumMessage", WellKnownObjectMode.Singleton);//調用系統自動建立,導致拿不到_remotingObject對象的執行個體化,這樣後期綁定事件就無法操作下去了,當然也可以直接靜態事件綁定,這樣就不需要手動執行個體化對象了
通過該方法手動建立_remotingObject這個對象的執行個體化。
然後之前講到了一個SwapObject這個類,這個類的作用是事件交換。
_remotingObject.ServerToClient +=方法();//這樣因為這個方法是用戶端的,服務端無法調用,所以需要一個中間轉換的 SwapObject swap = new SwapObject();//先建立一個Swap對象 _remotingObject.ServerToClient += swap.ToClient; //然後服務端事件發資訊給swap,然後swap再通過事件發訊息給用戶端,swap是用戶端建立的所以可以發送,而swap是服務端的類,所以服務端也能識別,swap起到了中間過渡的作用 swap.SwapServerToClient +=方法();