>백엔드 개발 >C#.Net 튜토리얼 >양방향 통신을 구현하는 C#NetRemoting 샘플 코드

양방향 통신을 구현하는 C#NetRemoting 샘플 코드

黄舟
黄舟원래의
2017-03-27 11:03:191822검색

이 글에서는 주로 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);
}
rrree

첫 번째 클래스는 일부 인터페이스와 일부 대리자를 정의하지만 실질적인 내용은 없습니다.

두 번째 클래스는 이전 인터페이스 클래스의 ToClient의 이벤트와 메서드를 정의합니다.

그리고 ICommand 인터페이스를 통합한 실질적인 데이터 클래스가 있습니다

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;
  } 
}

이 클래스는 MarshalByRefObject를 통합합니다

Remoting에서 전달한 객체는 참조이므로 전달된 Remote 객체 클래스는 MarshalByRefObject를 상속해야 합니다. MarshalByRefObject에 대한 MSDN의 설명은 다음과 같습니다. MarshalByRefObject는 프록시를 사용하여 메시지를 교환함으로써 응용 프로그램 도메인 경계를 넘어 통신하는 개체의 기본 클래스입니다. MarshalByRefObject에서 상속되지 않는 개체는 값에 따라 암시적으로 마샬링됩니다. 원격 애플리케이션이 값으로 마샬링된 개체를 참조하면 개체의 복사본이 원격 경계를 넘어 전달됩니다. 복사 메서드 대신 프록시 메서드를 사용하여 통신하려면 MarshallByRefObject를 상속해야 합니다.

이 클래스는 주로 클라이언트가 이벤트를 트리거하는 몇 가지 메소드인 ToServer, ToClient, ToLogin, ToExit 및 일부 이벤트, 클라이언트가 서버로 보내는 이벤트, 서버가 클라이언트로 보내는 이벤트를 정의합니다.

_nameHash는 로그인한 사용자를 기록합니다.

다음 단계는 클라이언트와 서버입니다.

먼저 서버:

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>();
  }
}

클라이언트:

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;


  }
}

서버 구현 단계:

1. 채널

애플리케이션 도메인 간에 통신하려면 채널을 구현해야 합니다. 앞서 언급했듯이 Remoting은 TcpChannel과 HttpChannel이라는 두 가지 유형의 채널을 포함하는 IChannel 인터페이스를 제공합니다. 직렬화된 데이터의 성능과 형식이 다르다는 점을 제외하면 이 두 유형은 완전히 동일한 방식으로 구현되므로 아래에서는 TcpChannel을 예로 들어 보겠습니다.

TcpChannel을 등록하려면 먼저 프로젝트에 "System.Runtime.Remoting" 참조를 추가한 다음 System.Runtime.Remoting.Channel.Tcp 네임스페이스를 사용하세요. 코드는 다음과 같습니다.

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;

  }
}

채널 객체를 인스턴스화할 때 포트 번호를 매개변수로 전달합니다. 그런 다음 정적 메서드 RegisterChannel()을 호출하여 채널 개체를 등록합니다.

2. 원격 개체 등록

채널 등록 후 원격 개체를 활성화하려면 해당 개체를 채널에 등록해야 합니다. 활성화 모드에 따라 객체를 등록하는 방법이 다릅니다.

(1) SingleTon 모드

WellKnown 개체의 경우 정적 메서드 RemotingConfiguration.RegisterWellKnownServiceType()을 통해 달성할 수 있습니다.

TcpChannel channel = new TcpChannel(8022);
ChannelServices.RegisterChannel(channel);

( 2) SingleCall 모드

객체 등록 방법은 기본적으로 SingleTon 모드와 동일하며, 열거 매개변수 WellKnownObjectMode만 SingleCall로 변경하면 됩니다.

RemotingConfiguration.RegisterWellKnownServiceType(
        typeof(ServerRemoteObject.ServerObject),
        "ServiceMessage",WellKnownObjectMode.SingleTon);

클라이언트 구현 단계:

1. 등록 채널:

RemotingConfiguration.RegisterWellKnownServiceType(
        typeof(ServerRemoteObject.ServerObject),
        "ServiceMessage",WellKnownObjectMode.SingleCall);

참고: 언제 클라이언트는 채널을 인스턴스화하고 기본 생성자 를 호출합니다. 즉, 포트 번호가 전달되지 않습니다. 실제로 이 포트 번호는 필수이지만 해당 사양은 나중에 Uri의 일부로 배치됩니다.

2. 원격 객체를 획득합니다.

서버 측과 마찬가지로 다양한 활성화 모드에 따라 클라이언트 구현 방식이 결정됩니다. 그러나 이러한 차이점은 WellKnown 활성화 모드와 클라이언트 활성화 모드 사이에서만 발생합니다. SingleTon 및 SingleCall 모드의 경우 클라이언트 구현은 완전히 동일합니다.

(1) WellKnown 활성화 모드

서버 측에서 잘 알려진 원격 개체를 얻으려면 Activator의 GetObject() 메서드를 통해 얻을 수 있습니다. process:

TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);

먼저 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으로 문의하세요.