C#NetRemoting two-way communication

When I have nothing to do, I want to play with two-way communication and realize the function of sending messages to each other similar to QQ. So I started learning .Net Remoting.

.Net Remoting is achieved by the client accessing the channel to obtain the server object through Remoting, and then parsing it into the client object through the proxy to achieve communication. That issay the object is created by the server.

Code first

First is the ICommand library

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>
        /// Addition operation
        /// </summary>
        /// <param name="x1">Parameter 1</param>
        /// <param name="x2">Parameter 2</param>
        /// <returns></returns>
        string SUM(int x1, int x2);
        /// <summary>
        /// Get the server event list
        /// </summary>
        Delegate[] GetServerEventList();
        /// <summary>
        /// Send a message
        /// </summary>
        /// <param name="info"></param>
        /// <param name="toName"></param>
        void ToServer(object info, string toName);
        /// <summary>
        /// Receive information
        /// </summary>
        /// <param name="info"></param>
        /// <param name="toName"></param>
        void ToClient(object info, string toName);
        void ToLogin(string name);
        void ToExit(string name);
    }
    /// <summary>
    /// Client sends message
    /// </summary>
    /// <param name="info">Information</param>
    /// <param name="toName">Who to send to, "" means everyone, null means there is no receiving server to receive it, other means specifying someone</param>
    public delegate void SendHandler(object info, string toName);
    /// <summary>
    /// The client receives the message
    /// </summary>
    /// <param name="info">Information</param>
    /// <param name="toName">Who to send to, "" means everyone, null means there is no receiving server to receive it, other means specifying someone</param>
    public delegate void ReceiveHandler(object info, string toName);
    /// <summary>
    ///User information event
    /// </summary>
    /// <param name="name">Username</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>
        /// Receive information
        /// </summary>
        /// <param name="info"></param>
        /// <param name="toName"></param>
        public void ToClient(object info, string toName)
        {
            if (_receive != null)
                _receive(info, toName);
        }
        //Infinite life cycle
        public override object InitializeLifetimeService()
        {
            return null;
        }

        privateReceiveHandler_receive;
    }
}

The first class just defines some interfaces and some delegates, without anything substantial.

The second class defines the ToClient events and methods in the previous interface class, which will be discussed later.

Then there is the substantive data class integrating the ICommand interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICommand;

namespace NetRemoting
{
    public class RemotingObject : MarshalByRefObject, IRemotingObject
    {
        /// <summary>
        /// Send event
        /// </summary>
        public event SendHandler ClientToServer
        {
            add { _send + = value; }
            remove { _send -= value; }
        }
        /// <summary>
        /// Receive message events
        /// </summary>
        public event ReceiveHandler ServerToClient;
        /// <summary>
        /// Send event
        /// </summary>
        public event UserChangedHandler Login
        {
            add { _login + = value; }
            remove { _login -= value; }
        }
        /// <summary>
        /// Send event
        /// </summary>
        public event UserChangedHandler Exit
        {
            add { _exit + = value; }
            remove { _exit -= value; }
        }
        /// <summary>
        /// Addition operation
        /// </summary>
        /// <param name="x1">Parameter 1</param>
        /// <param name="x2">Parameter 2</param>
        /// <returns></returns>
        public string SUM(int x1, int x2)
        {
            return x1 + " + " + x2 + "=" + (x1 + x2);
        }
        /// <summary>
        /// Bind the event method for the server to send messages to the client
        /// </summary>
        /// <param name="receive">Receive event</param>
        public Delegate[] GetServerEventList()
        {
            return this.ServerToClient.GetInvocationList();
        }
        /// <summary>
        /// Send a message
        /// </summary>
        /// <param name="info"></param>
        /// <param name="toName"></param>
        public void ToServer(object info, string toName)
        {
            if (_send != null)
                _send(info, toName);
        }
        /// <summary>
        /// Receive message
        /// </summary>
        /// <param name="info"></param>
        /// <param name="toName"></param>
        public void ToClient(object info, string toName)
        {
            if (_receive != null)
                _receive(info, toName);
        }
        /// <summary>
        /// Log in
        /// </summary>
        /// <param name="name">Username</param>
        public void ToLogin(string name)
        {
            if (!_nameHash.Contains(name))
            {
                _nameHash.Add(name);
                if (_login != null)
                    _login(name);
            }
            else
            { throw new Exception("User already exists"); }
        }
        /// <summary>
        /// quit
        /// </summary>
        /// <param name="name">Username</param>
        public void ToExit(string name)
        {
            if (_nameHash.Contains(name))
            {
                _nameHash.Remove(name);
                if (_exit != null)
                    _exit(name);
            }
        }

        private SendHandler _send;
        privateReceiveHandler_receive;
        private UserChangedHandler _login;
        private UserChangedHandler _exit;
        private HashSet<string> _nameHash = new HashSet<string>();
    }
}

This class integrates MarshalByRefObject

Since the object passed by Remoting is by reference, the remote object class passed must inherit MarshalByRefObject. MSDN’s description of MarshalByRefObject is: MarshalByRefObject is the base class for objects that communicate across application domain boundaries by using proxies to exchange messages. Objects that do not inherit from MarshalByRefObject are implicitly marshaled by value. When a remote application references an object that is marshaled by value, a copy of the object is passed across the remoting boundary. Because you want to communicate using proxy methods instead of copy methods, you need to inherit MarshallByRefObject.

This class mainly defines some methods for the client to trigger events, ToServer, ToClient, ToLogin, ToExit and some events, events sent by the client to the server, and events sent by the server to the client.

_nameHash just records which users are logged in. 

Next is the client and server.

First the server side:

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
    {
        publicServer()
        {
            InitializeComponent();
            Initialize();
        }
        /// <summary>
        /// Register channel
        /// </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 solution
            /*Convert the given System.MarshalByRefObject to an instance of the System.Runtime.Remoting.ObjRef class with the specified URI.
              ObjRef: Stores all the information needed to generate a proxy to communicate with the remote object. */
            ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage");//b plan
            _remotingObject.ClientToServer + = (info, toName) =>
            {
                rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(info.ToString() + "\r\
"); }));
                SendToClient(info, toName);
            };
            _remotingObject.Login + = (name) =>
            {
                rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(name + "Login" + "\r\
"); }));
            };
            _remotingObject.Exit + = (name) =>
            {
                rxtInfo.Invoke((MethodInvoker)(() => { rxtInfo.AppendText(name + "Exit" + "\r\
"); }));
            };
        }
        /// <summary>
        /// Logout channel
        /// </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>
        /// Broadcast message
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            SendToClient(txtSend.Text, txtName.Text);
        }
        /// <summary>
        /// Send message to client
        /// </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>
        /// Initialization
        /// </summary>
        private void Initialize()
        {
            //Set deserialization level
            BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
            BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//Supports all types of deserialization, very high level
            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;


    }
}

Then the client:

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
    {
        publicClient()
        {
            InitializeComponent();
        }
        /// <summary>
        /// Register channel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Client_Load(object sender, EventArgs e)
        {
            try
            {
                //Set deserialization level
                BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
                BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
                serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//Supports all types of deserialization, very high level
                //channel port
                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\
"); };
                SwapObject swap = new SwapObject();
                _remotingObject.ServerToClient + = swap.ToClient;
                swap.SwapServerToClient + = (info, toName) =>
                {
                    rtxMessage.Invoke((MethodInvoker)(() =>
                {
                    if (toName == txtLogin.Text || toName == "")
                        rtxMessage.AppendText(info + "\r\
");
                }));
                };
            }
            catch (Exception ex)
            { MessageBox.Show(ex.Message); }
        }
        /// <summary>
        /// Log in
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnLogin_Click(object sender, EventArgs e)
        {
            try
            {
                if (txtLogin.Text == "")
                    throw new Exception("Username cannot be empty");
                _remotingObject.ToLogin(txtLogin.Text);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        /// <summary>
        /// quit
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Client_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                _remotingObject.ToExit(txtLogin.Text);
            }
            catch
            { }
        }
        /// <summary>
        /// send
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            //rtxMessage.AppendText(_remotingObject.SUM(2, 4) + "\r\
");
            _remotingObject.ToServer(txtSend.Text, txtName.Text);
        }


        private IRemotingObject _remotingObject;

    }
}

Server-side implementation steps:

1. Registration channel

To communicate across application domains, channels must be implemented. As mentioned before, Remoting provides the IChannel interface, which contains two types of channels: TcpChannel and HttpChannel. Except for the different performance and format of serialized data, these two types are implemented in exactly the same way, so below we will take TcpChannel as an example.

To register TcpChannel, first add a reference to “System.Runtime.Remoting” in the project, and then use the namespace: System.Runtime.Remoting.Channel.Tcp. code show as below:
TcpChannel channel = new TcpChannel(8022);
ChannelServices.RegisterChannel(channel);

When instantiating the channel object, pass the port number as a parameter. Then call the static method RegisterChannel() to register the channel object.

2. Register remote objects

After registering the channel, to be able to activate the remote object, the object must be registered in the channel. Depending on the activation mode, the methods for registering objects are different.

(1) SingleTon mode

For WellKnown objects, this can be achieved through the static method RemotingConfiguration.RegisterWellKnownServiceType(): RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerRemoteObject.ServerObject),
“ServiceMessage”,WellKnownObjectMode.SingleTon);

(2)SingleCall mode

The method of registering an object is basically the same as the SingleTon mode. You only need to change the enumeration parameter WellKnownObjectMode to SingleCall. RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerRemoteObject.ServerObject),
“ServiceMessage”,WellKnownObjectMode.SingleCall);

Client implementation steps:

1. Registration channel:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);

Note that when the client instantiates the channel, the default constructor is called, that is, no port number is passed. In fact, this port number is indispensable, but its specification is placed later as part of the Uri.
2. Obtain the remote object.

Like the server side, different activation modes determine how the client is implemented. However, this difference is only between WellKnown activation mode and client activation mode. For SingleTon and SingleCall modes, the client implementation is exactly the same.

(1) WellKnown activation mode

To obtain the well-known remote object on the server side, you can obtain it through the GetObject() method of the Activator process:
ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(
typeof(ServerRemoteObject.ServerObject), “tcp://localhost:8080/ServiceMessage”);

First activate in WellKnown mode. The way for the client to obtain the object is to use GetObject(). The first parameter is the type of the remote object. The second parameter is the server-side uri. If it is an http channel, naturally use http://localhost:8022/ServiceMessage. Because I am using the local machine, here is localhost. You can replace it with the specific server IP address. The port must be consistent with the server port. What follows is the remote object service name defined by the server, that is, the content of the ApplicationName attribute.

 //Set the deserialization level
                BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
                BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
                serverProvider.TypeFilterLevel = TypeFilterLevel.Full;//Supports all types of deserialization, very high level
                //channel port
                IDictionary idic = new Dictionary<string, string>();
                idic["name"] = "clientHttp";
                idic["port"] = "0";
                HttpChannel channel = new HttpChannel(idic, clientProvider, serverProvider);

From the above code, you can see that the registration method has changed. That is because the client will report an error “Type deserialization not allowed” when registering events on the server.

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter//Binary
System.Runtime.Serialization.Formatters.Soap.SoapFormatter//soap
System.Xml.Serialization.XmlSerializer//xml

Another thing to note is:

ObjRef objRef = RemotingServices.Marshal(_remotingObject, "SumMessage");<br>//RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject), "SumMessage", WellKnownObjectMode.Singleton);<br>//Call the system to automatically create, resulting in the inability to get the instantiation of the _remotingObject object, so that late binding events cannot be operated. Of course, static event binding can also be done directly, so that there is no need to manually instantiate the object</pre >
  
 <p>Manually create an instantiation of the _remotingObject object through this method.</p>
 <p>Then we talked about the SwapObject class before. The function of this class is event exchange.</p>
  
  <pre> _remotingObject.ServerToClient + = method();<br>//This method is client-side and cannot be called by the server, so an intermediate conversion is required
 SwapObject swap = new SwapObject();//Create a Swap object first
 _remotingObject.ServerToClient + = swap.ToClient;//Then the server event sends a message to swap, and then swap sends a message to the client through the event. Swap is created by the client, so it can be sent, and swap is a class of the server, so the service It can also be recognized by the terminal, and swap plays the role of intermediate transition.
 swap.SwapServerToClient + = method();

The above are the learning results of two days of .Net Remoting.

Finally attach the source code

Custom channel properties: https://msdn.microsoft.com/zh-cn/library/bb397847(v=vs.100).aspx