C#NetRemoting双向通信
闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能。于是乎开始学习.Net Remoting.
.Net Remoting 是由客户端通过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;
}
}
第一个类就是定义一些接口,和一些委托,没有实质性的东西。
第二个类是定义了上一个接口类中的ToClient的事件和方法,作用之后会讲到。
然后就是集成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>();
}
}
该类集成了MarshalByRefObject
由于Remoting传递的对象是以引用的方式,因此所传递的远程对象类必须继承MarshalByRefObject。MSDN对MarshalByRefObject的说明是:MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承MarshallByRefObject。
该类主要是定义了一些方法用于客户端触发事件,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"] = "";
_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"] = "";
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提供了IChannel接口,分别包含TcpChannel和HttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外,实现的方式完全一致,因此下面我们就以TcpChannel为例。
注册TcpChannel,首先要在项目中添加引用“System.Runtime.Remoting”,然后using名字空间:System.Runtime.Remoting.Channel.Tcp。代码如下:
TcpChannel channel = new TcpChannel(8022);
ChannelServices.RegisterChannel(channel);
在实例化通道对象时,将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。
2、注册远程对象
注册了通道后,要能激活远程对象,必须在通道中注册该对象。根据激活模式的不同,注册对象的方法也不同。
(1) SingleTon模式
对于WellKnown对象,可以通过静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现:RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerRemoteObject.ServerObject),
"ServiceMessage",WellKnownObjectMode.SingleTon);
(2)SingleCall模式
注册对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerRemoteObject.ServerObject),
"ServiceMessage",WellKnownObjectMode.SingleCall);
客户端实现步骤:
1、注册通道:
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"] = "";
HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider);
从上述代码中可以看到注册方式有所变化,那是因为客户端注册服务端的事件时会报错“不允许类型反序列化”。
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter//二进制
System.Runtime.Serialization.Formatters.Soap.SoapFormatter//soap
System.Xml.Serialization.XmlSerializer//xml
还有一个需要注意的是:
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 +=方法();
以上是两天.Net Remoting的学习结果。
最后附上源码
自定义信道属性:https://msdn.microsoft.com/zh-cn/library/bb397847(v=vs.100).aspx
C#NetRemoting双向通信的更多相关文章
- C# 实现Remoting双向通信
本篇文章主要介绍了C# 实现Remoting双向通信,.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的 闲来无事想玩玩双向通 ...
- C# Remoting双向通信
闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能.于是乎开始学习.Net Remoting. .Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为 ...
- Apache thrift RPC 双向通信
在上一篇介绍Apache thrift 安装和使用,写了一个简单的demo,讲解thrift服务的发布和客户端调用,但只是单向的客户端发送消息,服务端接收消息.而客户端却得不到服务器的响应. 在不涉及 ...
- 用.NET MVC实现长轮询,与jQuery.AJAX即时双向通信
两周前用长轮询做了一个Chat,并移植到了Azure,还写了篇博客http://www.cnblogs.com/indream/p/3187540.html,让大家帮忙测试. 首先感谢300位注册用户 ...
- 【原创】NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
概述 本文演示的是一个Android客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo. 当前由于NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能. ...
- 【原创】NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
前言 本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo.服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了.同 ...
- 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...
- 【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
申明:本文由作者基于日常实践整理,希望对初次接触MINA.Netty的人有所启发.如需与作者交流,见文签名,互相学习. 学习交流 更多学习资料:点此进入 推荐 移动端即时通讯交流: 215891622 ...
- MVC5使用SignalR进行双向通信(1)
MVC5使用SignalR进行双向通信 (1) 配置Signal 在NuGet中通过 install-package Microsoft.AspNet.SignalR 命令进行安装 在Scripts文 ...
随机推荐
- php_memcache 缓存 下载-安装-配置-学习
一.安装php_memcache.dll 打开phpinfo() 查看PHP Extension Build,如TS,VC11 查看Architecture,如X86.X64 查看PHP版本,如5.6 ...
- 模拟HTTP请求获取返回值
package org.jeecgframework.core.util; import java.io.BufferedReader; import java.io.IOException; imp ...
- 计算机通信协议之OSI参考模型
OSI参考模型 在OSI参考模型之前人类对计算机结构的研究就已经进行了太多的讨论,最终通过了作为通信协议设计指标的OSI参考模型.这个协议将通信协议中必要的功能分成了七个部分.通过这些分层使得那些比较 ...
- 禁止用户使用密码的方式登录服务器,让其使用key的方式登录
这里我的远程登录工具xshell 所以这里我要做的是 第一步是在xshell中配置公私钥 密钥类型选择为rsa,长度为2048,并点击下一步,如下所示: 继续下一步 如果给密钥设置了密码,那么在使用这 ...
- 修改ultisnips的默认键
把ultisnips修改和textmate一致. <tab>展开代码,再按<tab>跳转到下一个占位符,<shift+tab>跳转上一个占位符. 在vim配置文件中 ...
- 使用 NGINX 和 NGINX Plus 的 Ingress Controller 进行 Kubernetes 的负载均衡
运行和管理跨机器集群的大规模的容器微服务应用是一个极具挑战的任务.Kubernetes 提供了一个强大的容器编排解决方案,从而帮助我们迎接这个挑战.它包含了一些重要特性,比如容错,自动伸缩,滚动升级, ...
- X64 Deep Dive
zhuan http://www.codemachine.com/article_x64deepdive.html X64 Deep Dive This tutorial discusses some ...
- jdk初始安装配置
Eclipse 3.7:http://www.eclipse.org/downloads/ JDK 6:http://www.oracle.com/technetwork/java/javase/do ...
- 每日英语:The Biggest Distraction In The Office Is Sitting Next To You
The big push in office design is forcing co-workers to interact more. Cubicle walls are lower, offic ...
- tortoisegit 7步 学会git分支
tortoisegit 7步 学会git分支 做自己的产品,分支还是很重要的.例如,我发布了一个app,但是有bug,又想做新功能,怎么办呢?如果只在一个git上开发的话,bug会越来越多,原来bug ...