首页 后端开发 C#.Net教程 C#NetRemoting实现双向通信的示例代码

C#NetRemoting实现双向通信的示例代码

Mar 27, 2017 am 11:03 AM

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

闲来无事想玩玩双向通信,实现类似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通道,自然是用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 +=方法();
登录后复制

以上是C#NetRemoting实现双向通信的示例代码的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
使用 C# 的活动目录 使用 C# 的活动目录 Sep 03, 2024 pm 03:33 PM

使用 C# 的 Active Directory 指南。在这里,我们讨论 Active Directory 在 C# 中的介绍和工作原理以及语法和示例。

C# 中的随机数生成器 C# 中的随机数生成器 Sep 03, 2024 pm 03:34 PM

C# 随机数生成器指南。在这里,我们讨论随机数生成器的工作原理、伪随机数和安全数的概念。

C# 数据网格视图 C# 数据网格视图 Sep 03, 2024 pm 03:32 PM

C# 数据网格视图指南。在这里,我们讨论如何从 SQL 数据库或 Excel 文件加载和导出数据网格视图的示例。

C# 中的阶乘 C# 中的阶乘 Sep 03, 2024 pm 03:34 PM

C# 阶乘指南。这里我们讨论 C# 中阶乘的介绍以及不同的示例和代码实现。

c#多线程和异步的区别 c#多线程和异步的区别 Apr 03, 2025 pm 02:57 PM

多线程和异步的区别在于,多线程同时执行多个线程,而异步在不阻塞当前线程的情况下执行操作。多线程用于计算密集型任务,而异步用于用户交互操作。多线程的优势是提高计算性能,异步的优势是不阻塞 UI 线程。选择多线程还是异步取决于任务性质:计算密集型任务使用多线程,与外部资源交互且需要保持 UI 响应的任务使用异步。

C# 中的模式 C# 中的模式 Sep 03, 2024 pm 03:33 PM

C# 模式指南。在这里,我们讨论 C# 中模式的介绍和前 3 种类型,以及其示例和代码实现。

C# 中的质数 C# 中的质数 Sep 03, 2024 pm 03:35 PM

C# 素数指南。这里我们讨论c#中素数的介绍和示例以及代码实现。

xml怎么改格式 xml怎么改格式 Apr 03, 2025 am 08:42 AM

可以采用多种方法修改 XML 格式:使用文本编辑器(如 Notepad )进行手工编辑;使用在线或桌面 XML 格式化工具(如 XMLbeautifier)进行自动格式化;使用 XML 转换工具(如 XSLT)定义转换规则;或者使用编程语言(如 Python)进行解析和操作。修改时需谨慎,并备份原始文件。

See all articles