初探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得到的对象,去触发事件,而事件的订阅端为客户端。为了说明问题,我重新命名了一些函数和事件名,代码如下:
远程对象:
-
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双向通信(四) 2013年06月26日 11:11:32 喜欢特别冷的冬天下着雪 阅读数 2632 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...
- 初探remoting双向通信(一)
原 初探remoting双向通信(一) 2013年06月24日 15:47:07 喜欢特别冷的冬天下着雪 阅读数 4389 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...
- 初探Remoting双向通信(二)
原 初探Remoting双向通信(二) 2013年06月25日 11:46:24 喜欢特别冷的冬天下着雪 阅读数 2977 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...
- 初探JavaScript(三)——JS带我"碰壁"带我飞
已经写了两篇关于小白的JavaScript之行,不可否认,每一种语言都有其精华与糟粕之处,来不及细细体味其精华奥妙,也没法对其评头论足,只能先了解,后深入.到目前为止已经看完<JavaScrip ...
- C# 实现Remoting双向通信
本篇文章主要介绍了C# 实现Remoting双向通信,.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的 闲来无事想玩玩双向通 ...
- Linux内核初探 之 进程(三) —— 进程调度算法
一.基本概念 抢占 Linux提供抢占式多任务,基于时间片和优先级对进程进行强制挂起 非抢占的系统需要进程自己让步(yielding) 进程类型 IO消耗型 经常处于可运行态,等待IO操作过程会阻塞 ...
- C# Remoting双向通信
闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能.于是乎开始学习.Net Remoting. .Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为 ...
- javascript --- 原型初探七日谈(三)
原型陷阱: 在处理原型问题上时,我们要注意两种行为. 1. 当我们对原型对象执行完全替换的时候,有可能会触发原型链的某种异常. 2. prototype.constructor 属性是不可靠的. 下面 ...
- CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113
gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...
随机推荐
- docker--shell和Exec格式
shell格式 RUN apt-get install -y vim CMD echo "docker so easy" ENTRYPOINT echo "docker ...
- 如何使用 C++ Inja html template 模板
C++ html template Inja是现代C ++的模板引擎,受到jinja for python的启发.它有一个简单而强大的模板语法,包含所有变量,循环,条件,包含,回调,您需要的注释,嵌套 ...
- java反射-学习
使用Java反射机制可以在运行时期获取Java类的信息,可获取以下相关的内容: Class对象 类名 修饰符 包信息 父类 实现的接口 构造器 方法 变量 注解 简单的反射例子: 1.获取class对 ...
- Groovy学习:第一章 用Groovy简化Java代码
1. Groovy的安装 目前Groovy的最新版本为2.1.2版,下载地址为:http://groovy.codehaus.org/Download下载后解压groovy-binary-2.1.2. ...
- js中的script标签属性
HTML <script> 元素用于嵌入或引用可执行脚本. 在html中插入一个script标签 <script src="index.js" sync cros ...
- Dagger2 探索记3——两大进阶组件(一)
今天要讲的时@Scope这个组件.为什么说它是进阶组件,就是因为它基本上没作用,但在理解了基本组件之后又必须用到. Scope的意思是作用域,一般用来标记@Provide方法,将生成的对象单例化.但@ ...
- 转载:jQuery的deferred对象详解
一.什么是deferred对象? 开发网站的过程中,我们经常遇到某些耗时很长的javascript操作.其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们 ...
- VUE前端面试题
什么是 mvvm? MVVM 是 Model-View-ViewModel 的缩写.mvvm 是一种设计思想.Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑:View ...
- C 给定路径遍历目录下的所有文件
在此之前需要了解 WIN32_FIND_DATA的结构 以及 FindFirstFile. FindNextFile原型以及用法注意事项传送门如下 https://msdn.microsoft.co ...
- 【JavaWeb项目】一个众筹网站的开发(三)第一个网页
一.bootstrap 本项目采用bootstrap3 bootstrap中文网 https://www.bootcss.com/ 使用bootstrap三步: 1.导入jQuery 2.导入boot ...