この記事では主に、双方向通信を実現する C# NetRemoting を紹介します。.Net Remoting は、クライアントが Remoting 経由でチャネルにアクセスし、サーバー objectを作成し、プロキシ経由でクライアントオブジェクトに解決して通信を実装します
何もすることがない時に双方向通信で遊びたくて、メッセージを送る機能を実現しましたQQ と同様に、.Net Remoting について学び始めました。
.Net 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; } }
インターフェイス といくつかのデリゲートを定義するだけで、実質的なものは何も定義しません
関数については後述します次に、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>(); } }
reference
の形式であるため、渡されるリモート オブジェクト クラスはMarshalByRefObject を継承する必要があります。MarshalByRefObject の MSDN の説明は次のとおりです。 MarshalByRefObject は、プロキシを使用してアプリケーション ドメインの境界を越えて通信するオブジェクトの基本クラスです。 MarshalByRefObject は値によって暗黙的にマーシャリングされます。リモート アプリケーションがマーシャリングされたオブジェクトを参照すると、コピー メソッドではなくプロキシ メソッドを使用して通信する必要があるため、This を継承する必要があります。クラスは基本的に定義されており、クライアントによってトリガーされるイベント、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 は、TcpChannel と HttpChannel という 2 種類のチャネルを含む IChannel インターフェイスを提供します。ただし、シリアル化されたデータのパフォーマンスと形式は異なります。完全に実装されているため、以下では TcpChannel を例として取り上げます。TcpChannel を登録するには、まず参照「System.Runtime.Remoting」をプロジェクトに追加し、次に名前空間 System.Runtime.Remoting.Channel.Tcp を使用します。コードは次のとおりです:
TcpChannel channel = new TcpChannel(8022); ChannelServices.RegisterChannel(channel);
static
メソッド RegisterChannel() を呼び出してチャネル オブジェクトを登録します。 チャンネルを登録した後、リモートオブジェクトをアクティブにするために、オブジェクトをチャンネルに登録する必要があります。起動モードに応じて、オブジェクトの登録方法が異なります。(1) SingleTon モード
WellKnown オブジェクトの場合、静的メソッド RemotingConfiguration.RegisterWellKnownServiceType() によって実装できます:RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleTon);
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleCall);
クライアントの実装
手順:
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通道,自然是用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 +=方法();
以上がC#NetRemoting は双方向通信のためのサンプル コード共有を実装しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。