zoukankan      html  css  js  c++  java
  • 初探Remoting双向通信(三)

    初探Remoting双向通信(三)

                        版权声明:本文为博主原创文章,未经博主允许不得转载。                        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

  • 相关阅读:
    免费申请域名
    分享学习linux网站
    二分法
    node 解决存储xss风险报告
    cf987f AND Graph
    loj2587 「APIO2018」铁人两项
    luogu3830 [SHOI2012]随机树
    luogu3343 [ZJOI2015]地震后的幻想乡
    bzoj2560 串珠子
    luogu3317 [SDOI2014]重建
  • 原文地址:https://www.cnblogs.com/owenzh/p/11175462.html
Copyright © 2011-2022 走看看