初探Remoting双向通信(三)

2013年06月25日 17:51:08
喜欢特别冷的冬天下着雪
阅读数 4741

                    版权声明:本文为博主原创文章,未经博主允许不得转载。                        https://blog.csdn.net/kkkkkxiaofei/article/details/9169433                    </div>
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-cd6c485e8b.css">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-cd6c485e8b.css">
<div class="htmledit_views" id="content_views">

三、利用事件实现服务器向客户端通信

按照之前的思路,这次利用Marshal得到的对象,去触发事件,而事件的订阅端为客户端。为了说明问题,我重新命名了一些函数和事件名,代码如下:

远程对象:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace Remoting
  6. {
  7. public delegate void MyDelegate(string msg);
  8. public class RemotingObject:MarshalByRefObject
  9. {
  10. public event MyDelegate SubscribeAtServer;//在客户端触发,在服务器订阅的事件
  11. public event MyDelegate SubscribeAtClient;//在服务器触发,在客户端订阅的事件
  12. //客户端触发事件
  13. public void TriggerAtClient(string msg)
  14. {
  15. if (SubscribeAtServer != null)
  16. SubscribeAtServer(msg);
  17. }
  18. //服务器触发事件
  19. public void TriggerAtServer(string msg)
  20. {
  21. if (SubscribeAtClient != null)
  22. SubscribeAtClient(msg);
  23. }
  24. //无限生命周期
  25. public override object InitializeLifetimeService()
  26. {
  27. return null;
  28. }
  29. }
  30. }

服务器:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Runtime.Remoting;
  10. using System.Runtime.Remoting.Channels;
  11. using System.Runtime.Remoting.Channels.Tcp;
  12. using Remoting;
  13. namespace Server
  14. {
  15. public partial class ServerForm : Form
  16. {
  17. RemotingObject marshal_obj;
  18. public ServerForm()
  19. {
  20. InitializeComponent();
  21. StartServer();
  22. }
  23. //开启服务器
  24. public void StartServer()
  25. {
  26. //注册信道
  27. TcpChannel tcpchannel = new TcpChannel(8080);
  28. ChannelServices.RegisterChannel(tcpchannel,false);
  29. //服务器获取远程对象
  30. marshal_obj = new RemotingObject();
  31. ObjRef objRef = RemotingServices.Marshal(marshal_obj, "url");
  32. //服务器绑定客户端触发的事件
  33. marshal_obj.SubscribeAtServer+=new MyDelegate(marshal_obj_SubscribeAtServer);
  34. }
  35. void marshal_obj_SubscribeAtServer(string msg)
  36. {
  37. //跨线程调用
  38. textBox2.Invoke(new Action<string>(str => { textBox2.AppendText(str); }), msg);
  39. }
  40. private void 广播发送_Click(object sender, EventArgs e)
  41. {
  42. marshal_obj.TriggerAtServer("服务器--" + this.ServerIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine);
  43. }
  44. //获取本地ip
  45. public string ServerIP()
  46. {
  47. return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString();
  48. }
  49. }
  50. }

客户端:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Runtime.Remoting;
  10. using System.Runtime.Remoting.Channels;
  11. using System.Runtime.Remoting.Channels.Tcp;
  12. using Remoting;
  13. namespace Client
  14. {
  15. public partial class ClientForm : Form
  16. {
  17. RemotingObject obj;
  18. public ClientForm()
  19. {
  20. InitializeComponent();
  21. StartClient();
  22. }
  23. private void 发送_Click(object sender, EventArgs e)
  24. {
  25. obj.TriggerAtClient("客户端--" + this.ClientIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine);
  26. }
  27. //开启客户端
  28. public void StartClient()
  29. {
  30. //注册信道
  31. TcpChannel tcpchannel = new TcpChannel(0);
  32. ChannelServices.RegisterChannel(tcpchannel, false);
  33. //获取代理
  34. obj = (RemotingObject)Activator.GetObject(typeof(RemotingObject), "tcp://localhost:8080/url");
  35. //订阅服务器事件
  36. obj.SubscribeAtClient += new MyDelegate(obj_SubscribeAtClient);
  37. }
  38. void obj_SubscribeAtClient(string msg)
  39. {
  40. textBox1.Invoke(new Action<string>((str) => { textBox1.AppendText(str); }),msg);
  41. }
  42. //获取本地ip
  43. public string ClientIP()
  44. {
  45. return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString();
  46. }
  47. }
  48. }

这时我很忐忑的运行代码,结果表明我的担心不是多余的。在客户端报错如下,

简单点说,这个错误的意思就是客户端获取的那个代理对象,用它来订阅服务器的事件是订阅不到的。

想想似乎也对,之前客户端触发事件,服务器去订阅,服务器能订阅到,那是因为客户端那个对象本身就是服务器端创建的那个对象,只是让它跑到客户端那里去触发了一个方法而已,它调用的所有东西都是服务器的(还记得我之前在第一篇中的那个return 0的猫腻吧)。但是现在反过来就不对了,服务器端触发事件是远程对象里的事件,远程对象想要被客户端访问就必须被序列化,原因就在于事件是基于委托的,而.net的委托一般是不能被序列化的。那么如何序列化委托和事件呢?

我查了资料,这就需要换一种方式注册信道,在注册信道的时候强制设置可序列化级别为"所有类型",这样就可以将事件序列化。

服务器端注册信道改为:


  1. //设置反序列化级别
  2. BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
  3. BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
  4. serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高
  5. //信道端口
  6. IDictionary idic = new Dictionary<string, int>();
  7. idic["port"]=8080;
  8. //注册信道
  9. TcpChannel tcpchannel = new TcpChannel(idic,clientProvider,serverProvider);
  10. ChannelServices.RegisterChannel(tcpchannel,false);

客户端注册信道改为:


  1. //设置反序列化级别
  2. BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
  3. BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
  4. serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高
  5. //信道端口
  6. IDictionary idic = new Dictionary<string, int>();
  7. idic["port"] = 0;
  8. //注册信道
  9. TcpChannel tcpchannel = new TcpChannel(idic, clientProvider, serverProvider);
  10. ChannelServices.RegisterChannel(tcpchannel,false);

注:客户端的端口号不能与服务器一致,否则将出现"通常每个套接字地址(协议/网络地址/端口)只允许使用一次"的异常,将其设置为0,则客户端自动选择可用的端口。

这时候我再次运行上面的代码,满怀期待,可还是在客户端出又报错了,如下,

又是这一句,没错,老是这里出错。不过这次比上次好多了,最起码报错不一样了,说明刚才那个问题解决了。现在仔细看看这次的错误提示,大概是说文件不存在于客户端,即找不到obj_SubscribeAtClient这个函数,无法订阅事件。

我不得不静下来好好整理下思路了:客户端获取的这个对象,我一致都强调它其实是在服务器存活的,只是获取了服务器的代理,同一个引用。虽然它跨越了程序域,并且你可以调用它在服务器的方法,而在客户端去获取某个返回值。但是并不代表你可以改变它,比如你对这个远程对象中的事件进行+操作,不就是改变了它的内部结构么,它是在服务器端的,它怎么可能知道有一个obj_SubscribeAtClient函数需要绑定呢?这个函数在服务器根本就不存在。

我又参考了许多资料,其中"虾皮"的博客很给力,找到了其中的关键技术。解决办法就是利用一个中间事件进行交换,有点难以理解奥。先代码把,在远程对象的程序集里再添加一个类,这个类就专门定义一个事件,这个事件和服务器端触发的事件一模一样,如下:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace Remoting
  6. {
  7. public class Swap : MarshalByRefObject
  8. {
  9. public event MyDelegate SwapSubscribeAtClient;//在服务器触发,在客户端订阅的事件
  10. //服务器触发事件
  11. public void TriggerAtServerSwapEvent(string msg)
  12. {
  13. if (SwapSubscribeAtClient != null)
  14. SwapSubscribeAtClient(msg);
  15. }
  16. //无限生命周期
  17. public override object InitializeLifetimeService()
  18. {
  19. return null;
  20. }
  21. }
  22. }

之前生成的远程对象的dll应该是RemotingObject.dll, 这时候也要换了,因为这时候dll里应该有两个类了,重新编译为Remoting.dll

下面说一下如何交换:

上面的Swap交换类同样继承了 MarshalByRefObject,说明它也可以跨程序域。如果客户订阅定事件的时候先订阅到Swap的对象上,然后Swap的事件才被客户端订阅,这样就利用一个交换机制实现了订阅。因为在订阅Swap的时候,服务器发现自己本地也有Swap,所以它可以找到,而这个Swap对象又恰恰是在客户端实例化的,所以Swap的对象也可以订阅客户端的事件。有点绕哈,看最新的代码吧,

远程对象:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace Remoting
  6. {
  7. public delegate void MyDelegate(string msg);
  8. public class RemotingObject:MarshalByRefObject
  9. {
  10. public event MyDelegate SubscribeAtServer;//在客户端触发,在服务器订阅的事件
  11. public event MyDelegate SubscribeAtClient;//在服务器触发,在客户端订阅的事件
  12. //服务器触发事件
  13. public void TriggerAtClient(string msg)
  14. {
  15. if (SubscribeAtServer != null)
  16. SubscribeAtServer(msg);
  17. }
  18. //客户端触发事件
  19. public void TriggerAtServer(string msg)
  20. {
  21. if (SubscribeAtClient != null)
  22. SubscribeAtClient(msg);
  23. }
  24. //无限生命周期
  25. public override object InitializeLifetimeService()
  26. {
  27. return null;
  28. }
  29. }
  30. }

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace Remoting
  6. {
  7. public class Swap : MarshalByRefObject
  8. {
  9. public event MyDelegate SwapSubscribeAtClient;//在服务器触发,在客户端订阅的事件
  10. //服务器触发事件
  11. public void TriggerAtServerSwapEvent(string msg)
  12. {
  13. if (SwapSubscribeAtClient != null)
  14. SwapSubscribeAtClient(msg);
  15. }
  16. //无限生命周期
  17. public override object InitializeLifetimeService()
  18. {
  19. return null;
  20. }
  21. }
  22. }

服务器:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Runtime.Remoting;
  10. using System.Runtime.Remoting.Channels;
  11. using System.Runtime.Remoting.Channels.Tcp;
  12. using Remoting;
  13. using System.Runtime.Serialization.Formatters;
  14. using System.Collections;
  15. namespace Server
  16. {
  17. public partial class ServerForm : Form
  18. {
  19. RemotingObject marshal_obj;
  20. public ServerForm()
  21. {
  22. InitializeComponent();
  23. StartServer();
  24. }
  25. //开启服务器
  26. public void StartServer()
  27. {
  28. //设置反序列化级别
  29. BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
  30. BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
  31. serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高
  32. //信道端口
  33. IDictionary idic = new Dictionary<string, int>();
  34. idic["port"]=8080;
  35. //注册信道
  36. TcpChannel tcpchannel = new TcpChannel(idic,clientProvider,serverProvider);
  37. ChannelServices.RegisterChannel(tcpchannel,false);
  38. //服务器获取远程对象
  39. marshal_obj = new RemotingObject();
  40. ObjRef objRef = RemotingServices.Marshal(marshal_obj, "url");
  41. //服务器绑定客户端触发的事件
  42. marshal_obj.SubscribeAtServer += new MyDelegate(marshal_obj_SubscribeAtServer);
  43. }
  44. void marshal_obj_SubscribeAtServer(string msg)
  45. {
  46. //跨线程调用
  47. textBox2.Invoke(new Action<string>(str => { textBox2.AppendText(str); }), msg);
  48. }
  49. private void 广播发送_Click(object sender, EventArgs e)
  50. {
  51. marshal_obj.TriggerAtServer("服务器--" + this.ServerIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine);
  52. }
  53. //获取本地ip
  54. public string ServerIP()
  55. {
  56. return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString();
  57. }
  58. }
  59. }

客户端:


  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Runtime.Remoting;
  10. using System.Runtime.Remoting.Channels;
  11. using System.Runtime.Remoting.Channels.Tcp;
  12. using Remoting;
  13. using System.Collections;
  14. using System.Runtime.Serialization.Formatters;
  15. namespace Client
  16. {
  17. public partial class ClientForm : Form
  18. {
  19. RemotingObject obj;
  20. public ClientForm()
  21. {
  22. InitializeComponent();
  23. StartClient();
  24. }
  25. private void 发送_Click(object sender, EventArgs e)
  26. {
  27. obj.TriggerAtClient("客户端--" + this.ClientIP() + System.Environment.NewLine + textBox1.Text + System.Environment.NewLine);
  28. }
  29. //开启客户端
  30. public void StartClient()
  31. {
  32. //设置反序列化级别
  33. BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
  34. BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
  35. serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高
  36. //信道端口
  37. IDictionary idic = new Dictionary<string, int>();
  38. idic["port"] = 0;
  39. //注册信道
  40. TcpChannel tcpchannel = new TcpChannel(idic, clientProvider, serverProvider);
  41. ChannelServices.RegisterChannel(tcpchannel,false);
  42. //获取代理
  43. obj = (RemotingObject)Activator.GetObject(typeof(RemotingObject), "tcp://localhost:8080/url");
  44. //订阅服务器事件
  45. Swap swap = new Swap();
  46. obj.SubscribeAtClient += new MyDelegate(swap.TriggerAtServerSwapEvent);
  47. swap.SwapSubscribeAtClient += new MyDelegate(obj_SubscribeAtClient);
  48. }
  49. void obj_SubscribeAtClient(string msg)
  50. {
  51. textBox1.Invoke(new Action<string>((str) => { textBox2.AppendText(str); }),msg);
  52. }
  53. //获取本地ip
  54. public string ClientIP()
  55. {
  56. return System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString();
  57. }
  58. }
  59. }

运行结果自然成功了,如下

这几乎可以让我以此为基础,对我的项目进行改写了,可是问题还是有的,下篇继续。

(注:上面的Demo在此处下载http://download.csdn.net/detail/kkkkkxiaofei/5648577

初探Remoting双向通信(三)的更多相关文章

  1. 初探Remoting双向通信(四)

    原 初探Remoting双向通信(四) 2013年06月26日 11:11:32 喜欢特别冷的冬天下着雪 阅读数 2632 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  2. 初探remoting双向通信(一)

    原 初探remoting双向通信(一) 2013年06月24日 15:47:07 喜欢特别冷的冬天下着雪 阅读数 4389 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  3. 初探Remoting双向通信(二)

    原 初探Remoting双向通信(二) 2013年06月25日 11:46:24 喜欢特别冷的冬天下着雪 阅读数 2977 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  4. 初探JavaScript(三)——JS带我"碰壁"带我飞

    已经写了两篇关于小白的JavaScript之行,不可否认,每一种语言都有其精华与糟粕之处,来不及细细体味其精华奥妙,也没法对其评头论足,只能先了解,后深入.到目前为止已经看完<JavaScrip ...

  5. C# 实现Remoting双向通信

    本篇文章主要介绍了C# 实现Remoting双向通信,.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的 闲来无事想玩玩双向通 ...

  6. Linux内核初探 之 进程(三) —— 进程调度算法

    一.基本概念 抢占 Linux提供抢占式多任务,基于时间片和优先级对进程进行强制挂起 非抢占的系统需要进程自己让步(yielding) 进程类型 IO消耗型 经常处于可运行态,等待IO操作过程会阻塞 ...

  7. C# Remoting双向通信

    闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能.于是乎开始学习.Net Remoting. .Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为 ...

  8. javascript --- 原型初探七日谈(三)

    原型陷阱: 在处理原型问题上时,我们要注意两种行为. 1. 当我们对原型对象执行完全替换的时候,有可能会触发原型链的某种异常. 2. prototype.constructor 属性是不可靠的. 下面 ...

  9. CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113

    gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...

随机推荐

  1. redis 入门之有序集合

    zadd 将一个或多个 member 元素及其 score 值加入到有序集 key 当中.如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 ...

  2. object of type 'Response' has no len()

    看见没,这里括号弄错了! 网上解释是requests.get()得到的是一个response对象,无法用BeautifulSoup解析,如果要解析,解析对象应该是requests.get().cont ...

  3. java中延迟任务的处理方式

    1.利用延迟队列 延时队列,第一他是个队列,所以具有对列功能第二就是延时,这就是延时对列,功能也就是将任务放在该延时对列中,只有到了延时时刻才能从该延时对列中获取任务否则获取不到…… 应用场景比较多, ...

  4. C#的扩展方法学习

    一,什么是扩展方法? 1,扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 2,扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用 ...

  5. JS异步事件顺序:setTimeout,async,promise

    为什么最近更新那么频繁,还不是因为笔试的时候瞎了? 先说异步事件执行顺序的规则: 1. 定时器异步队列和promise队列不是同一队列,promise优先级高于setTimeout; 2. 创建pro ...

  6. web前端架构师学习流程

    JavaScript 开发(高级) 系统知识 1.1ECMAScript标准的发展过程,ES6语言对JavaScript的改进: 1.2ES6中语法层面的新特性(let.const.参数扩展.模块化等 ...

  7. Java中File的处理

    不知道“文件”和“文件路径”是否存在的处理情况 1.如果是文件,先获取文件父路径,没有就生成父路径,然后再生成文件. public class TestMain { public static voi ...

  8. python 环境安装说明

    一.        安装 python-3.6.5-amd64.exe   1. 安装完成后,设置系统环境变量 Path E:\Programs\Python\Python36; E:\Program ...

  9. bzoj 2015

    http://www.lydsy.com/JudgeOnline/problem.php?id=2015 裸最短路(' '     ) 不过我最初以为是mst (' '    ) #include & ...

  10. hdu 6134 Battlestation Operational (莫比乌斯反演+埃式筛)

    Problem Description   > The Death Star, known officially as the DS-1 Orbital Battle Station, also ...