最近有人問到一個老骨董 .NET Remoting,當需要跨程式資料交換,它是一個選項
Wiki 這樣介紹它
以下內容出自:https://zh.wikipedia.org/zh-tw/.NET_Remoting
『.NET遠端處理[1](.NET Remoting )是微軟 .NET Framework 中的一種網路通訊技術,與 XML Web Service 不同的是,它可以使用 SOAP 以外的協定來通訊,而在伺服端和用戶端之間所操作的方法近乎相同,用戶端可以不必考慮使用的協定,即可存取伺服端所開放的物件。這個技術與是由 Distributed COM所發展而來的,與DCOM最大的不同是,DCOM有限制使用 TCP Port,但.NET Remoting 可以選擇使用 TCP 或 HTTP 的方式通訊,而資料可以利用 SOAP 或二進位傳輸方式在網路上流動,二進位的傳輸效能是 SOAP 所不能比的,但 SOAP 卻可以得到和 Web Service 相互溝通的能力,因此 .NET Remoting 的設計彈性較大。』
在 .NET Remoting 中是利用通道 (channel) 來實現兩個應用程式域之間物件的通訊。下圖出自 https://zh.wikipedia.org/wiki/.NET_Remoting
.NET Remoting 有兩種通道
Tcp 和 Http,System.Runtime.Remoting.Channel 中定義了 IChannel 介面
TcpChannel
- 命名空間:System.Runtime.Remoting.Channel.Tcp.TcpChannel
- 使用 Socket 傳輸序列化訊息流,預設使用二進位序列化,具有更高的傳輸效能。
HttpChannel
- 命名空間:System.Runtime.Remoting.Channel.Tcp.HttpChannel
- HttpChannel 使用Http協議,(可穿越防火牆, 80 Port),傳輸序列化訊息流,預設使用 Soap 序列化。
遠端物件的啟用
伺服器端啟用
又叫做 WellKnow ,根據 URI 和埠號發佈服務,有兩種模式
- SingleCall:在每一次用戶端呼叫時都生成一個執行個體 (無狀態)。
- SingleTon:在第一次呼叫時就生成執行個體,之後每一次呼叫都使用相同的執行個體。
客戶端啟用
- 用 Activator.GetObject 建立通道,只能呼叫預設的建構函數,部署用戶端程式較方便
- 用 Activator.CreateInstance 建立通道,可呼叫傳遞參數給建構函數,部署用戶端程式較不方便
伺服器端和用戶端的傳遞
遠端物件必須要繼承 System.MarshalByRefObject 類別,System.MarshalByRefObject 用來跨應用程式的通訊基礎類別
若需要用類別或是結構,作為參數需要加上 [Serializable]
[Serializable]
public class Person
{
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
}
實作步驟
Lab.NetRemoting.Core
新增 ITrMessage.cs,內容如下
namespace Lab.NetRemoting.Core
{
public interface ITrMessage
{
string GetName();
DateTime GetNow();
Person GetPerson();
}
}
Lab.NetRemoting.Implement
建立類別庫專案,名為 Lab.NetRemoting.Implement
參考
- Lab.NetRemoting.Core
- Faker.Net from nuget,Install-Package Faker.Net
並新增以下 TrMessage.cs,TrMessage 實作了 MarshalByRefObject,它需要讓 Client 和 Server 使用
namespace Lab.NetRemoting.Implement
{
public class TrMessage : MarshalByRefObject, ITrMessage
{
private readonly string _name = "yao";
public TrMessage()
{
Console.WriteLine($"{DateTime.Now:yyyy/MM/dd hh:mm:ss}, Create Construct");
}
public TrMessage(string name)
{
this._name = name;
Console.WriteLine($"{DateTime.Now:yyyy/MM/dd hh:mm:ss}, Create Construct pass {name}");
}
public string GetName()
{
Console.WriteLine($"{DateTime.Now:yyyy/MM/dd hh:mm:ss}, Call GetName Method");
return this._name;
}
public DateTime GetNow()
{
Console.WriteLine($"{DateTime.Now:yyyy/MM/dd hh:mm:ss}, Call GetNow Method");
return DateTime.Now;
}
public Person GetPerson()
{
var person = new Person
{
Name = Faker.Name.FullName(),
Gender = Faker.Boolean.Random() ? "M" : "F",
Age = Faker.RandomNumber.Next(1, 100)
};
return person;
}
}
}
Lab.NetRemoting.Server
建立 Console App 專案,名為 Lab.NetRemoting.Server
參考
- Lab.NetRemoting.Implement
- System.Runtime.Remoting
建立 TcpChannel 並註冊,代碼如下:
namespace Lab.NetRemoting.Server
{
internal class Program
{
private static void Main(string[] args)
{
try
{
var port = 9527;
var tcpChannel = new TcpChannel(port);
ChannelServices.RegisterChannel(tcpChannel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(TrMessage)
, "RemotingTest", WellKnownObjectMode.Singleton);
Console.WriteLine("按任意鍵離開!");
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
註冊通道
var port = 9527;
var tcpChannel = new TcpChannel(port);
ChannelServices.RegisterChannel(tcpChannel, false);
註冊遠端物件
註冊了通道後,要能啟用遠端物件,必須在通道中註冊該物件。根據啟用模式的不同,註冊物件的方法也不同。
SingleTon 模式
- 在每一次用戶端呼叫時都生成一個執行個體 (無狀態)。
- "RemotingTest" 是 Uri,用戶端訪問的位置
- 通過靜態方法 RemotingConfiguration.RegisterWellKnownServiceType() 來實現
RemotingConfiguration.RegisterWellKnownServiceType(typeof(TrMessage), "RemotingTest", WellKnownObjectMode.SingleTon);
- 另一種作法則是透過 RemotingServices.Marshal() 來實現,這裡要實體化 TrMessage
var message = new TrMessage();
var objRef = RemotingServices.Marshal(message, "RemotingTest");
Console.WriteLine($"{DateTime.Now}, Server 已啟動");
Console.WriteLine("按任意鍵離開!");
Console.ReadLine();
RemotingServices.Disconnect(message);
SingleCall 模式
跟 SingleTon 差不多只是 WellKnownObjectMode
舉列換成 SingleCall
RemotingConfiguration.RegisterWellKnownServiceType(typeof(TrMessage), "RemotingTest", WellKnownObjectMode.SingleCall);
客戶端啟用模式
"RemotingTest" 是 Url
RemotingConfiguration.ApplicationName = "RemotingTest";
RemotingConfiguration.RegisterActivatedServiceType(typeof(TrMessage));
Lab.NetRemoting.Client
建立 WinFrom App 專案,名為 Lab.NetRemoting.Client
參考
- Lab.NetRemoting.Core
代碼如下:
namespace Lab.NetRemoting.Client
{
public partial class Form1 : Form
{
private readonly ITrMessage _trMessage;
private readonly string _url = "tcp://127.0.0.1:9527/RemotingTest";
public Form1()
{
this.InitializeComponent();
//WellKnown 啟用模式,在客戶端建立物件時,只能呼叫預設的建構函式
this._trMessage = (ITrMessage) Activator.GetObject(typeof(ITrMessage), this._url);
Console.WriteLine($"{DateTime.Now}, 已連接伺服器:{this._url}");
}
private void button1_Click(object sender, EventArgs e)
{
try
{
var now = this._trMessage.GetNow();
var name = this._trMessage.GetName();
var person = this._trMessage.GetPerson();
var msg = $"Hi, my name is {name}\r\n" +
$"Now:{now:yyyy-MM-dd hh:mm:ss}\r\n" +
$"Data:{JsonConvert.SerializeObject(person)}";
//MessageBox.Show(msg);
Console.WriteLine(msg);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
取得得遠端物件
WellKnown 啟用模式
用 System.Activator.GetObject() 方法,來獲得伺服器端的具名遠端物件
private readonly string _url = "tcp://127.0.0.1:9527/RemotingTest";
private readonly ITrMessage _trMessage;
public Form1()
{
this.InitializeComponent();
if (this._trMessage == null)
{
this._trMessage = (ITrMessage) Activator.GetObject(typeof(ITrMessage), this._url);
Console.WriteLine($"{DateTime.Now}, 已連接伺服器:{this._url}");
}
}
執行畫面如下:
客戶端啟用模式
WellKnown模式在客戶端建立物件時,只能呼叫預設的建構函式。
//客戶端啟用模式
RemotingConfiguration.RegisterActivatedClientType(typeof(ITrMessage), this._url);
要傳入參數給建構函數,必須要依賴 Lab.NetRemoting.Implement.TrMessage Class,將來它有變化時,用戶端的部署難度就會增加,這點必須要考量
直接 new
this._trMessage = new TrMessage("余小章");
動態實體化遠端物件
//動態實體化遠端物件
object[] attrs = {new UrlAttribute(this._url)};
var objs = new object[1];
objs[0] = "余小章";
this._trMessage = (ITrMessage) Activator.CreateInstance(typeof(TrMessage), objs, attrs);
為了讓客戶端能傳入參數呼叫建構函數,又不需要依賴 Lab.NetRemoting.Implement.TrMessage class
在 Lab.NetRemoting.Core 專案新增 ITrMessageFactory.cs
namespace Lab.NetRemoting.Core
{
public interface ITrMessageFactory
{
string Url { get; set; }
ITrMessage CreateInstance();
ITrMessage CreateInstance(string name);
}
}
Lab.NetRemoting.Implement 專案新增 TrMessageFactory.cs
namespace Lab.NetRemoting.Core
{
public class TrMessageFactory : MarshalByRefObject, ITrMessageFactory
{
public string Url { get; set; }
public ITrMessage CreateInstance()
{
return new TrMessage();
}
public ITrMessage CreateInstance(string name)
{
return new TrMessage(name);
}
}
}
伺服器端原本註冊 TrMessage 改成 TrMessageFactory
RemotingConfiguration.RegisterWellKnownServiceType(typeof(TrMessageFactory),
"RemotingTest",
WellKnownObjectMode.Singleton);
用戶端原本依賴 ITrMessage 改成 ITrMessageFactory
var serverFactory = (ITrMessageFactory)Activator.GetObject(typeof(ITrMessageFactory), this._url);
serverFactory.Url = this._url;
this._trMessage = serverFactory.CreateInstance("余小章");
透過這樣的技巧,只要在兩邊協議的合約不動情況下,TrMessage 調整的細節都不會影響用戶端
如下圖,建構函數注入了余小章
參考連結
https://zh.wikipedia.org/wiki/.NET_Remoting
https://www.itread01.com/article/1464570786.html
範例專案
https://github.com/yaochangyu/sample.dotblog/tree/master/Remoting/Lab.NetRemoting
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET