在Windows操作系统中,每一个应用程序都是相互独立的,它们拥有独立的内存空间,各个应用程序之间形成一道边界,不能互相访问和操作,这是操作系统为了保护应用程序的安全而设计的。这种看似“井水不犯河水”的设计同样有它的弊端,假如两个应用程序需要相互协作配合才能完成工作,那它们就需要进行通信和数据交互,今天以一个简单的小例子,简述一种基于.NET的进程间通信的方案Remoting技术,仅供学习分享使用,如有不足之处,还请指正。
Remoting概述
Remoting是Mircrosoft提供的一种基于.Net框架的分布式处理方式,它允许对象通过应用程序域与另一对象进行交互。在Remoting中,是通过通道(Channel)来实现两个应用程序域之间对象的通信的,如下图所示:
Remoting知识点
在使用Remoting技术开发应用之前,需要了解Remoting的相关概念和知识点:
- IChannel(通道):它是Remoting技术的核心内容,是客户端和服务端的数据交互的桥梁,Remoting主要包含两种Channel类型:Tcp和Http。Tcp通道提供了基于Socket的传输工具,它使用Tcp协议来跨越Remoting边界传输序列化后的消息流。TcpChannel类型默认使用二进制格式序列化消息对象,因此它具有更高的传输性能。HttpChannel提供了一种使用Http协议且能在Internet上穿越防火墙传输序列化消息流,默认情况下,HttpChannel采用Soap格式序列化消息对象,因此它具有很好的互操作性。通常情况下,在局域网内,更多的使用TcpChannel;在Internet下,则采用HttpChannel。
- 远程对象激活方式:在访问远程类型的一个对象实例之前,必须通过一个名为Activation的进程创建它并进行初始化,这种客户端通过通道来创建远程对象,称为对象的激活。在Remoting中,远程对象的激活分为两大类:服务器端激活和客户端激活。服务端激活根据指定的端口和地址来发布对象,分为SingleTon和SingleCall两种模式。SingleTon模式为有状态模式,Remoting为所有的客户端建立同一个对象实例,当远程对象处于活动状态时,SingleTon实例会处理所有后来的客户端请求,而不管他们是不是同一个客户端,它类似于Asp.NET中的Application状态。SingleCall模式是一种无状态模式,Remoting会为每一个访问的客户端建立一个远程对象实例,至于对象实例的销毁则由GC自动管理,它类似于Asp。net中的Session状态管理。
- 远程对象:客户端在获取服务器端对象时,并不是获取实际的远程对象,而是它的引用。因此在Remoting中,所传递的对象类必须派生自MarshalByRefObject。MSDN对MarshalByRefObject的说明是:MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。
Remoting实例
在本示例的解决方案中,主要包含4个项目,分别如下所示:
- 服务端Okcoder.Remoting.Server,主要用于Remoting服务的发布,接收和处理客户端请求。
- 客户端Okcoder.Remoting.Client,主要用于Remoting服务的调用,用于从服务端获取数据。
- 接口Okcoder.Remoting.Interface,用于定义模型和远程对象接口,它是客户端和服务端共同的接口。将远程对象抽离成独立的接口,主要是为了代码的安全性,且降低客户端对远程对象元数据的相关性。
- 实现Okcoder.Remoting.Imps,主要用于封装对接口的业务实现,对客户端来说,它是不公开的。
具体项目结构如下图所示:
接口定义
在Remoting中,客户端调用的是远程对象数据的引用,所以远程对象数据的定义必须在客户端和服务器端保持一致,否则将无法调用。且为了数据的安全性和相关性,我们将远程对象抽离成接口,如下所示:- namespace Okcoder.Remoting.Interface
- {
- public interface IServerObject
- {
- Person GetPerson(string name, string sex, int age);
- }
- }
复制代码 其中Person对象是我们定义的一个模型类,由于它需要在Remoting客户端和服务器端进行传递,所以需要声明为可序列化,如下所示:- namespace Okcoder.Remoting.Interface
- {
- [Serializable]
- public class Person
- {
- public string Name { get; set; }
- public string Sex { get; set; }
- public int Age { get; set; }
- public override string ToString()
- {
- return $"我是{Name},年龄是{Age},我是{Sex}";
- }
- }
- }
复制代码 为了保护远程对象IServerObject实现方式,我们定义了IServerOjbectFactory接口,它主要实现远程对象的创建,如下所示:- namespace Okcoder.Remoting.Interface
- {
- public interface IServerObjectFactory
- {
- IServerObject CreateInstance();
- }
- }
复制代码
接口实现
客户端只关注接口的定义,并不关注接口功能的实现,所以接口实现只在服务端调用,如下所示:
远程对象的实现,它实现IServerObject接口,实现具体的业务功能,由于它需要在Remoting的客户端和服务器端传递引用,所以必须继承自MarshalByRefObject,如下所示:- using Okcoder.Remoting.Interface;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace Okcoder.Remoting.Imps
- {
- public class ServerObject:MarshalByRefObject,IServerObject
- {
- public Person GetPerson(string name,string sex,int age)
- {
- Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} GetPerson::Name={name},Sex={sex},Age={age}");
- Person person = new Person()
- {
- Name = name,
- Sex = sex,
- Age = age,
- };
- return person;
- }
- }
- }
复制代码 远程对象工厂的实现,它主要用于创建远程对象,同样也需要继承自MarshalByRefObject,如下所示:- using Okcoder.Remoting.Interface;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace Okcoder.Remoting.Imps
- {
- public class ServerObjectFactory :MarshalByRefObject, IServerObjectFactory
- {
- public IServerObject CreateInstance()
- {
- Console.WriteLine("创建ServiceObject");
- return new ServerObject();
- }
- }
- }
复制代码
Remoting服务端
服务端主要创建通道,注册通道,以及注册服务。- TcpChannel channel = new TcpChannel(8080);
- ChannelServices.RegisterChannel(channel,false);
复制代码 注册服务分为两种方式:
服务端激活模式,如下所示:- RemotingConfiguration.RegisterWellKnownServiceType(typeof(ServerObjectFactory), "ServiceMessage", WellKnownObjectMode.Singleton);//Singleton模式
复制代码 本示例采用TcpChannel,基于Socket进行消息流传递,端口号为8080, 并将ServerObjectFactory注册成服务端激活远程对象,其中WellKnownObjectMode为激活模式,包含SingleTom和SingleCall两种,本示例采用SingleTon模式。
客户端激活模式
对于客户端激活模式,使用的方法又有不同,但区别不大,看了代码就一目了然。- RemotingConfiguration.ApplicationName = "ServiceMessage";
- RemotingConfiguration.RegisterActivatedServiceType(typeof(ServerObject));
复制代码 为什么要在注册对象方法前设置ApplicationName属性呢?其实这个属性就是该对象的URI。对于WellKnown模式,URI是放在RegisterWellKnownServiceType()方法的参数中,当然也可以拿出来专门对ApplicationName属性赋值。而RegisterActivatedServiceType()方法的重载中,没有ApplicationName的参数,所以必须分开。
注销通道
在Remoting中当我们注册通道的时候,就自动开启了通道的监听。而如果关闭了对通道的监听,则该通道就无法接受客户端的请求,但通道仍然存在,如果你想再一次注册该通道,会抛出异常。- IChannel[] channels = ChannelServices.RegisteredChannels;
- foreach (var item in channels)
- {
- if (item.ChannelName == "MyTcp")
- {
- TcpChannel tcp = (TcpChannel)item;
- tcp.StopListening(null);
- ChannelServices.UnregisterChannel(item);
- }
- }
复制代码
Remoting客户端
客户端主要做两件事,一是注册通道。Remoting中服务器端和客户端都必须通过通道来传递消息,以获得远程对象。第二步则是获得该远程对象。
注册通道,在客户端定义通道不需要传递端口号,因为在获取对象时会的URI中包含端口号,如下所示:- TcpChannel channel = new TcpChannel();
- ChannelServices.RegisterChannel(channel,false);
复制代码 对于服务端激活模式,获取远程对象,通过Activator对象的GetOjbect方法获取远程对象。第一个参数为远程对象类型,第二个参数为远程对象URI。本示例获取IServerObjectFactory的远程对象,如下所示:- IServerObjectFactory serverObjectFactory = (IServerObjectFactory)Activator.GetObject(typeof(IServerObjectFactory), "tcp://localhost:8080/ServiceMessage");//WellKnown激活模式
- IServerObject serverObject = serverObjectFactory.CreateInstance();
- Person person = serverObject.GetPerson("okcoder", "male", 28);
- Console.Write($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 当前获取的对象为:{person.ToString()}");
复制代码 获取远程对象后,即可获取服务端远程对象的引用,然后就可以调用远程对象的方法,就像操作本地对象一样。本示例先调用serverObjectFactory的CreateInstance方法获取远程对象,再调用ServerObject的GetPerson方法获取对象。
对于客户端激活模式,获取IServerObjectFactory远程对象的引用,如下所示:- object[] attrs = { new UrlAttribute("tcp://localhost:8080/ServiceMessage") };
- object[] objs = new object[0];
- IServerObjectFactory serverObjectFactory = (IServerObjectFactory)Activator.CreateInstance(typeof(IServerObjectFactory), objs, attrs);
复制代码 当然也可以通过如下方式:- object[] attrs = { new UrlAttribute("tcp://localhost:8080/ServiceMessage") };
- ObjectHandle objHandle = Activator.CreateInstance("Okcoder.Remoting.Interface", "Okcoder.Remoting.Interface.IServerObjectFactory", attrs);
- IServerObjectFactory serverObject3 = (IServerObjectFactory)objHandle.Unwrap();
复制代码 注册不同通道,在注册通道时,还可以为通道指定名称,端口号,所传递消息流的协议等具体内容:- //注册Tcp通道
- IDictionary tcpProp = new Hashtable();
- tcpProp["name"] = "tcp9090";
- tcpProp["port"] = 9090;
- IChannel channel1 = new TcpChannel(tcpProp, new BinaryClientFormatterSinkProvider(), new BinaryServerFormatterSinkProvider());
- ChannelServices.RegisterChannel(channel1, false);
- //注册Http通道
- IDictionary httpProp = new Hashtable();
- httpProp["name"] = "http8080";
- httpProp["port"] = 8080;
- IChannel channel2 = new HttpChannel(httpProp, new SoapClientFormatterSinkProvider(), new SoapServerFormatterSinkProvider());
- ChannelServices.RegisterChannel(channel2, false);
复制代码
实例演示
在本实例中,服务端通常是长时间运行,供客户端随时调用,一般部署成后台服务,或宿主到指定容器,或命令行窗口的形式等内容,如下所示:
客户端通常是其他形式的应用程序,如控制台,WinForm,WPF等,如下所示:
如上图所示,当客户端调用时,服务器端会根据访问的URI返回指定的远程对象引用,则表示成功。
补充内容
需要注意的是,Remoting技术是基于.NET Framework框架的分布式数据处理框架;在最新的.NET框架中,则已经不再使用此技术,如果实现此类功能,则可以采用SignalR,GRPC等技术。
以上就是《推荐一款基于.NET的进程间通信框架》的全部内容,旨在抛砖引玉,一起学习,共同进步。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |