推薦一款基于.NET的進程間通信框架
在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的進程間通信框架》的全部內容,旨在拋磚引玉,一起學習,共同進步。
作者:老碼識途
出處:http://www.rzrgm.cn/hsiang/
本文版權歸作者和博客園共有,寫文不易,支持原創,歡迎轉載【點贊】,轉載請保留此段聲明,且在文章頁面明顯位置給出原文連接,謝謝。
關注個人公眾號,定時同步更新技術及職場文章

浙公網安備 33010602011771號