初探Remoting双向通信(三)
来源:互联网 发布:华为java编码规范考试 编辑:程序博客网 时间:2024/04/30 06:35
三、利用事件实现服务器向客户端通信
按照之前的思路,这次利用Marshal得到的对象,去触发事件,而事件的订阅端为客户端。为了说明问题,我重新命名了一些函数和事件名,代码如下:
远程对象:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Remoting{ public delegate void MyDelegate(string msg); public class RemotingObject:MarshalByRefObject { public event MyDelegate SubscribeAtServer;//在客户端触发,在服务器订阅的事件 public event MyDelegate SubscribeAtClient;//在服务器触发,在客户端订阅的事件 //客户端触发事件 public void TriggerAtClient(string msg) { if (SubscribeAtServer != null) SubscribeAtServer(msg); } //服务器触发事件 public void TriggerAtServer(string msg) { if (SubscribeAtClient != null) SubscribeAtClient(msg); } //无限生命周期 public override object InitializeLifetimeService() { return null; } }}
服务器:
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.Tcp;using Remoting;namespace Server{ public partial class ServerForm : Form { RemotingObject marshal_obj; public ServerForm() { InitializeComponent(); StartServer(); } //开启服务器 public void StartServer() { //注册信道 TcpChannel tcpchannel = new TcpChannel(8080); ChannelServices.RegisterChannel(tcpchannel,false); //服务器获取远程对象 marshal_obj = new RemotingObject(); ObjRef objRef = RemotingServices.Marshal(marshal_obj, "url"); //服务器绑定客户端触发的事件 marshal_obj.SubscribeAtServer+=new MyDelegate(marshal_obj_SubscribeAtServer); } void marshal_obj_SubscribeAtServer(string msg) { //跨线程调用 textBox2.Invoke(new Action<string>(str => { textBox2.AppendText(str); }), msg); } private void 广播发送_Click(object sender, EventArgs e) { marshal_obj.TriggerAtServer("服务器--" + this.ServerIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine); } //获取本地ip public string ServerIP() { return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString(); } }}
客户端:
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.Tcp;using Remoting;namespace Client{ public partial class ClientForm : Form { RemotingObject obj; public ClientForm() { InitializeComponent(); StartClient(); } private void 发送_Click(object sender, EventArgs e) { obj.TriggerAtClient("客户端--" + this.ClientIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine); } //开启客户端 public void StartClient() { //注册信道 TcpChannel tcpchannel = new TcpChannel(0); ChannelServices.RegisterChannel(tcpchannel, false); //获取代理 obj = (RemotingObject)Activator.GetObject(typeof(RemotingObject), "tcp://localhost:8080/url"); //订阅服务器事件 obj.SubscribeAtClient += new MyDelegate(obj_SubscribeAtClient); } void obj_SubscribeAtClient(string msg) { textBox1.Invoke(new Action<string>((str) => { textBox1.AppendText(str); }),msg); } //获取本地ip public string ClientIP() { return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString(); } }}
这时我很忐忑的运行代码,结果表明我的担心不是多余的。在客户端报错如下,
简单点说,这个错误的意思就是客户端获取的那个代理对象,用它来订阅服务器的事件是订阅不到的。
想想似乎也对,之前客户端触发事件,服务器去订阅,服务器能订阅到,那是因为客户端那个对象本身就是服务器端创建的那个对象,只是让它跑到客户端那里去触发了一个方法而已,它调用的所有东西都是服务器的(还记得我之前在第一篇中的那个return 0的猫腻吧)。但是现在反过来就不对了,服务器端触发事件是远程对象里的事件,远程对象想要被客户端访问就必须被序列化,原因就在于事件是基于委托的,而.net的委托一般是不能被序列化的。那么如何序列化委托和事件呢?
我查了资料,这就需要换一种方式注册信道,在注册信道的时候强制设置可序列化级别为"所有类型",这样就可以将事件序列化。
服务器端注册信道改为:
//设置反序列化级别 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 //信道端口 IDictionary idic = new Dictionary<string, int>(); idic["port"]=8080; //注册信道 TcpChannel tcpchannel = new TcpChannel(idic,clientProvider,serverProvider); ChannelServices.RegisterChannel(tcpchannel,false);
客户端注册信道改为:
//设置反序列化级别 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 //信道端口 IDictionary idic = new Dictionary<string, int>(); idic["port"] = 0; //注册信道 TcpChannel tcpchannel = new TcpChannel(idic, clientProvider, serverProvider); ChannelServices.RegisterChannel(tcpchannel,false);
注:客户端的端口号不能与服务器一致,否则将出现"通常每个套接字地址(协议/网络地址/端口)只允许使用一次"的异常,将其设置为0,则客户端自动选择可用的端口。
这时候我再次运行上面的代码,满怀期待,可还是在客户端出又报错了,如下,
又是这一句,没错,老是这里出错。不过这次比上次好多了,最起码报错不一样了,说明刚才那个问题解决了。现在仔细看看这次的错误提示,大概是说文件不存在于客户端,即找不到obj_SubscribeAtClient这个函数,无法订阅事件。
我不得不静下来好好整理下思路了:客户端获取的这个对象,我一致都强调它其实是在服务器存活的,只是获取了服务器的代理,同一个引用。虽然它跨越了程序域,并且你可以调用它在服务器的方法,而在客户端去获取某个返回值。但是并不代表你可以改变它,比如你对这个远程对象中的事件进行+操作,不就是改变了它的内部结构么,它是在服务器端的,它怎么可能知道有一个obj_SubscribeAtClient函数需要绑定呢?这个函数在服务器根本就不存在。
我又参考了许多资料,其中"虾皮"的博客很给力,找到了其中的关键技术。解决办法就是利用一个中间事件进行交换,有点难以理解奥。先代码把,在远程对象的程序集里再添加一个类,这个类就专门定义一个事件,这个事件和服务器端触发的事件一模一样,如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Remoting{ public class Swap : MarshalByRefObject { public event MyDelegate SwapSubscribeAtClient;//在服务器触发,在客户端订阅的事件 //服务器触发事件 public void TriggerAtServerSwapEvent(string msg) { if (SwapSubscribeAtClient != null) SwapSubscribeAtClient(msg); } //无限生命周期 public override object InitializeLifetimeService() { return null; } }}
之前生成的远程对象的dll应该是RemotingObject.dll, 这时候也要换了,因为这时候dll里应该有两个类了,重新编译为Remoting.dll
下面说一下如何交换:
上面的Swap交换类同样继承了 MarshalByRefObject,说明它也可以跨程序域。如果客户订阅定事件的时候先订阅到Swap的对象上,然后Swap的事件才被客户端订阅,这样就利用一个交换机制实现了订阅。因为在订阅Swap的时候,服务器发现自己本地也有Swap,所以它可以找到,而这个Swap对象又恰恰是在客户端实例化的,所以Swap的对象也可以订阅客户端的事件。有点绕哈,看最新的代码吧,
远程对象:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Remoting{ public delegate void MyDelegate(string msg); public class RemotingObject:MarshalByRefObject { public event MyDelegate SubscribeAtServer;//在客户端触发,在服务器订阅的事件 public event MyDelegate SubscribeAtClient;//在服务器触发,在客户端订阅的事件 //服务器触发事件 public void TriggerAtClient(string msg) { if (SubscribeAtServer != null) SubscribeAtServer(msg); } //客户端触发事件 public void TriggerAtServer(string msg) { if (SubscribeAtClient != null) SubscribeAtClient(msg); } //无限生命周期 public override object InitializeLifetimeService() { return null; } }}
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Remoting{ public class Swap : MarshalByRefObject { public event MyDelegate SwapSubscribeAtClient;//在服务器触发,在客户端订阅的事件 //服务器触发事件 public void TriggerAtServerSwapEvent(string msg) { if (SwapSubscribeAtClient != null) SwapSubscribeAtClient(msg); } //无限生命周期 public override object InitializeLifetimeService() { return null; } }}
服务器:
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.Tcp;using Remoting;using System.Runtime.Serialization.Formatters;using System.Collections;namespace Server{ public partial class ServerForm : Form { RemotingObject marshal_obj; public ServerForm() { InitializeComponent(); StartServer(); } //开启服务器 public void StartServer() { //设置反序列化级别 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 //信道端口 IDictionary idic = new Dictionary<string, int>(); idic["port"]=8080; //注册信道 TcpChannel tcpchannel = new TcpChannel(idic,clientProvider,serverProvider); ChannelServices.RegisterChannel(tcpchannel,false); //服务器获取远程对象 marshal_obj = new RemotingObject(); ObjRef objRef = RemotingServices.Marshal(marshal_obj, "url"); //服务器绑定客户端触发的事件 marshal_obj.SubscribeAtServer += new MyDelegate(marshal_obj_SubscribeAtServer); } void marshal_obj_SubscribeAtServer(string msg) { //跨线程调用 textBox2.Invoke(new Action<string>(str => { textBox2.AppendText(str); }), msg); } private void 广播发送_Click(object sender, EventArgs e) { marshal_obj.TriggerAtServer("服务器--" + this.ServerIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine); } //获取本地ip public string ServerIP() { return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString(); } }}
客户端:
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.Tcp;using Remoting;using System.Collections;using System.Runtime.Serialization.Formatters;namespace Client{ public partial class ClientForm : Form { RemotingObject obj; public ClientForm() { InitializeComponent(); StartClient(); } private void 发送_Click(object sender, EventArgs e) { obj.TriggerAtClient("客户端--" + this.ClientIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine); } //开启客户端 public void StartClient() { //设置反序列化级别 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 //信道端口 IDictionary idic = new Dictionary<string, int>(); idic["port"] = 0; //注册信道 TcpChannel tcpchannel = new TcpChannel(idic, clientProvider, serverProvider); ChannelServices.RegisterChannel(tcpchannel,false); //获取代理 obj = (RemotingObject)Activator.GetObject(typeof(RemotingObject), "tcp://localhost:8080/url"); //订阅服务器事件 Swap swap = new Swap(); obj.SubscribeAtClient += new MyDelegate(swap.TriggerAtServerSwapEvent); swap.SwapSubscribeAtClient += new MyDelegate(obj_SubscribeAtClient); } void obj_SubscribeAtClient(string msg) { textBox1.Invoke(new Action<string>((str) => { textBox2.AppendText(str); }),msg); } //获取本地ip public string ClientIP() { return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString(); } }}
运行结果自然成功了,如下
这几乎可以让我以此为基础,对我的项目进行改写了,可是问题还是有的,下篇继续。
(注:上面的Demo在此处下载http://download.csdn.net/detail/kkkkkxiaofei/5648577)
- 初探Remoting双向通信(三)
- 初探remoting双向通信(一)
- 初探Remoting双向通信(二)
- 初探Remoting双向通信(四)
- [原创].NET Remoting: 如何通过Remoting实现双向通信(Bidirectional Communication)
- 如何通过Remoting实现双向通信
- 如何通过Remoting实现双向通信
- 企业开发基础设施--事件通知服务(Remoting双向通信)
- delphi prism 如何通过.net Remoting实现双向通信
- .Net Remoting的双向通信和Windows Service的宿主服务
- .Net Remoting的双向通信和Windows Service的宿主服务
- .Net Remoting的双向通信和Windows Service的宿主服务
- COM初探(三)
- tolua++初探(三)
- Scala初探(三)
- Oracle初探(三)
- 初探UiAutomator(三)
- 异常初探(三)
- 「Perl/Tk」一个数据库期末设计的前台
- MYSQL数据库里面的所有密码批量MD5加密
- RMAN 备份详解
- 腾讯的微信平台开放
- 提示框第三方库之MBProgressHUD
- 初探Remoting双向通信(三)
- C#调用Quartz实例代码
- VPN
- BT 协议调用流程
- How to Disassemble/Assemble Galaxy S4 i9500 for Screen/Parts Repair!
- java中filter的用法
- php进一法取整、四舍五入取整、忽略小数等的取整数方法大全
- VC++6.0注释快捷键设置和显示代码行号
- 想念清秋萧瑟