C# NetRemoting实现双向通信

 更新时间:2020年6月25日 11:20  点击:1489

闲来无事想玩玩双向通信,实现类似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"] = "8022";
      _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"] = "0";
        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"] = "0";
        HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider);

从上述代码中可以看到注册方式有所变化,那是因为客户端注册服务端的事件时会报错“不允许类型反序列化”。

还有一个需要注意的是:

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的学习结果。

最后附上源码:NetRemoting_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25