分布式|執行
遊客,您好!轉網通站 | 轉電信站 積木首頁 | 500多種網頁特效整理 | 實用查詢函數手冊 | 積木網BT下載聯盟 | 經典笑話 | 廣播電台 | 高清晰經典圖片素材
程式開發 網頁設計 搜尋引擎 特效代碼 作業系統 防範病毒 駭客技術 圖形圖象 電腦硬體 網路技術 服 務 器 數 據 庫 網文精粹
您的位置:積木首頁 >> 程式開發頻道 >> C# >> 本文: 標題:C#中的遠程執行和分散式運算時間:2006-8-6 來源:不詳 瀏覽數:5次
摘要
遠程執行是C#中一種使開發人員能夠使用遠程對象的基礎架構。遠程對象是一種位於調用者應用域之外的對象。本文中的例子說明了如何使用二種遠程對象訪問機制(值傳遞和地址傳遞),它還通過一個簡單的、功能強大的任務伺服器的實現說明了分散式運算中遠程對象的強大功能。
任務伺服器能夠接受所有能實現ITask介面的對象,並在其應用域中運行這些對象。更為重要的是,它能夠一次從多個用戶端接受任務。
在學習完本篇文章後,讀者將能夠:
━━建立伺服器/用戶端對象之間的串連。
━━按值傳遞對象。
━━按地址傳遞對象。
━━理解遠程任務分配的概念。
遠程對象
遠程對象通過地址傳遞對象或者通過對象的值傳遞對象。
在第一種情況下,對象的地址由應用域A傳遞到應用域B,但對象的方法調用在應用域A和應用域B之間。對象在應用域A中存在和運行,但在應用域B中也象是個本地的對象。
在第二種情況下,整個對象及其附屬的實體(被稱作對象圖表)被序列化成位元組的形式,並從應用域A被傳送到應用域B。然後,對象在應用域B被“反序列化”並恢複到原來的狀態。現在,對象就在應用域B上存在和運行了。
建立對象主機(也被稱作伺服器)
設立伺服器需要作的第一步是在對象進行通訊的二個應用域間建立一條通道,它可以是一條TCP/IP通道或HTTP通道。TCP通道的速度較快,適用於對網路內資訊包傳輸限制較少的網路使用,HTTP通道更靈活,適合在互連網等廣域網路上使用。
我們將使用TCP/IP通道,而且將在同一台機器的二個不同的應用網域名稱上同時運行伺服器端和客戶機端。因此,輸入下面的代碼在TCP/IP堆棧上的8065連接埠建立通道myChannel:
TcpChannel myChannel = new TcpChannel(8065);
下面是向.NET的通道服務註冊myChannel通道,這將使該通道可以在伺服器應用域之外被訪問。我們可以通過下面的代碼實現這一目的:
ChannelServices.RegisterChannel(myChannel);
最後一步是告訴.NET的遠程執行基礎架構有關我們要開放的對象的有關情況,我們需要公布對象的類型和位置,用戶端定位對象所使用的名稱和.NET的遠程基礎架構對對這一對象調用的處理方式。我們可以通過下面的代碼擷取對象的類型:
Type objectType = new MyCoolObject().GetType()
通過下面的代碼就可以向.NET遠和基礎架構註冊該對象:
RemotingConfiguration.RegisterWellKnownServiceType(
objectType, "MyCoolObject",
WellKnownObjectMode.Singleton);
對象的調用有二種處理方式:Singleton和SingleCall。在Singleton方式中,在第一次用戶端方法調用時建立對象,並保持對象存在直到用戶端中止串連或對象自然死亡;在SingleCall方式中,每次用戶端的方法調用都會建立對象,對象只在方法調用持續期間存在,一旦方法調用結束,對象就會死亡。SingleCall方式中,用戶端串連不會隨方法調用的結束而中止,只有對象會隨著方法調用的結束而被殺死。
為遠程對象建立用戶端
對用戶端的第一個要求是遠程對象的類要在用戶端的本機上,.NET遠程執行基礎架構代理將使用它對與遠程對象間傳遞的資訊進行解釋和裝配。
需要再次建立一個通道,然後向.NET遠程基礎架構進行註冊,使該通道成為可用的:
TcpChannel myChannel = new TcpChannel();
ChannelServices.RegisterChannel(myChannel);
需要注意的是,我們在建立通道時沒有指定連接埠地址。我們將在要求伺服器給出我們要調用的遠程對象的引用時指定連接埠地址,代碼如下所示:
MyCoolObject mine = (MyCoolObject)Activator.GetObject(
typeof(MyCoolObject),
"tcp://localhost:8085/MyCoolObject");
第一個參數擷取我們要定位的對象的類型,第二個參數指定遠程對象的URL。一旦我們得到該對象,就需要將其類型由普通的對象轉換為MyCoolObject類型。
現在,我們得到了位於伺服器端的遠程對象的引用,我們可以將該對象看作是本地的對象。
功能強大的任務伺服器
任務伺服器是一個如何利用.NET的按值傳遞遠程對象機制的執行個體。任務伺服器有一個名字為TaskRunner的對象,用戶端可以擷取它的引用,TaskRunner可以從用戶端接受任何實現ITask介面的對象。
ITask介面強迫用戶端使用Run()和Identify()二個方法建立任務對象,用戶端然後就可以建立一個複雜的、對資源敏感的實現ITask介面的任務,然後將它提交給任務伺服器,在它自己的應用域中執行。這意味著沒有充足計算資源的用戶端可以充分利用任務伺服器的資源執行自己無力完成的、比較複雜的任務。
因此,需要計算有3000位小數的圓周率的用戶端就可以建立一個完成相關計算和實現ITask介面的對象,該任務然後被使用TaskRunner對象提交給任務伺服器執行。
ITask介面強迫用戶端從它們的Run()方法返回一個對象,當然,該對象的值可能為空白,但也有可能會包含有意義的值。
簡單地說,按值傳遞的遠程執行最適合這樣一種情況,即伺服器沒有用戶端希望在遠程環境中執行的對象的明確的表示。
按值傳遞對象
一個對象被按值傳遞時,它需要被轉換成一種可以傳輸的格式。一個應用域中的對象佔用著一塊系統記憶體,而且能夠對其他對象發給它的訊息作出反應。要將該對象傳遞到其他應用域中,必須將它序列化。這意味著它必須能夠被分解為位元組流,然後能夠在目的地被重新組合為原來的對象。
要實現這一點,就必須使用編譯器可序列化的特性,使編譯器對一些結構進行呂行化處理。我們可以將整個類標記為可序列化的,也可以將包括一些選定的方法在內的類的一部分標記為可序列化的。在任務伺服器環境中,整個的用戶端任務對象必須能夠被序列化。代碼如下所示:
Using System;
?[Serializable()]
class ClientTask {
?
需要注意的是,如果一個對象有外部的庫檔案等附屬的部分,被該對象使用的部分也必須被序列化,這是在遠程應用域中重建立立對象所必需的。
運行例子程式
這個例子是用C# for .NET Beta 2編寫的,並在C# for .NET Beta 2中通過了測試。
為了使用任務伺服器,我們必須以下面的順序對程式進行編譯:
csc /target:library /out:ITask.dll ITask.cs
csc /target:library /out:TaskRunner.dll TaskRunner.cs
csc /r:ITask.dll /r:TaskRunner.dll TaskServer.cs
csc /r:ITask.dll /r:TaskRunner.dll TaskClient.cs
上面的順序非常重要,因為TaskServer和TaskClient類要用到TaskRunner和ITask庫。
要運行這個例子程式,首先啟動伺服器代碼,並等待出現下面的提示:
[i] Task Server Started, press to exit?
然後就可以運行用戶端程式了。
在TaskClient類中有一個實現ITask介面而且能夠可序列化的類,該任務將求出二個數的積,並返回一個int類型的對象。我建議讀者建立自己的任務,並試著在任務伺服器上運行它,這將使你親身體會到這種分散式運算的巨大威力。它可以使用戶端軟體開發人員充分利用任務伺服器上豐富的資源完成複雜的、需要大量資源的任務。
我們可以利用任務伺服器完成諸如數文書處理、壓縮、加密、排序等各種操作。
正面是本文中例子所涉及的原始碼,及其簡單的註解,供有興趣的讀者參考:
// ITask.cs
// 使用者可以使用該介面來建立自己的任務,它完成下面的二種操作:
// ·伺服器可以通過調用方法Run()來運行建立的任務。
// ·用戶端可以保證任務從方法Run()中啟動。
// 其中還有一個Identify()方法,伺服器用它顯示一些有關任務的資訊。
// 該介面被編譯為同一個名字空間下的單獨的庫檔案,使得任務伺服器的管理員能夠將該介面作為所有能夠在//他的任務伺服器上啟動並執行任務的契約進行分發。
// 用戶端將繼承該類,建立自己的任務對象,提交給伺服器運行。
namespace TaskServer {
// 必須將它定義為一個介面
public interface ITask {
object Run();
string Identify();
}
}
// TaskRunner.cs
// 該對象用來運行由用戶端提交的任務,提交的任務將在伺服器的應用域執行。
// TaskRunner對象以引用的方式傳遞給用戶端,無需對它進行序列化
// TaskRunner接受所有實現ITask介面的任務,它需要二個參數:Run()和Identify()。
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace TaskServer {
public class TaskRunner : MarshalByRefObject {
ITask remoteTaskObject;
public TaskRunner() {
Console.WriteLine("\n[i] TaskRunner activated");
}
public string LoadTask(ITask task) {
Console.WriteLine("\n[i] Loading new task...");
if(task == null) {
Console.WriteLine("[e] Task reference is NULL. Task not Loaded.");
return "[e] Task not loaded.";
}
remoteTaskObject = task;
Console.WriteLine("[i] Task has been loaded.");
Console.WriteLine("[i] Task ID: " + remoteTaskObject.Identify() + "\n");
return "[i] Task loaded. Welcome to the All Powerful TaskServer.";
}
public object RunTask() {
Console.WriteLine("\n[i] Running the task...");
object result = remoteTaskObject.Run();
Console.WriteLine("[i] Task finished.");
return result;
}
}
}
// TaskServerEngine.cs
// 這個類用來啟動任務伺服器應用程式。它建立C#遠程執行的後端,載入通道,註冊TaskRunner對象,然後讓 //遠程執行機制的後端監測TCP通道上的串連請求
using System;
using System.IO;
// 下面的庫用於向遠程執行機制註冊我們的對象
using System.Runtime.Remoting;
// 下面的庫用於向通道服務註冊我們的TCP通道裝置
using System.Runtime.Remoting.Channels;
// 該庫提供了用來與遠程應用域(用戶端)通訊所需要的TCP通道
using System.Runtime.Remoting.Channels.Tcp;
namespace TaskServer {
public class TaskServerEngine {
// 我們只需要一個方法,它可以是靜態,因為我們不需要建立這個類的執行個體,該方法的作用僅僅是 //建立並創始化TaskServerEngine。
public static void Main() {
// 向使用者表明我們正在啟動伺服器類
Console.WriteLine("The All Powerful Task Server!");
Console.WriteLine("....\"Your Task is My Command\"....");
Console.WriteLine("\n[i] Starting Task Server...");
try {
// 建立TCP通道
Console.WriteLine("[i] -- Creating TCP channel");
TcpChannel chan = new TcpChannel(8085);
// 向.NET的遠程服務註冊新建立的TcpChannel,使用戶端可以使用這一服務
Console.WriteLine("[i] -- Registering TCP channel");
ChannelServices.RegisterChannel(chan);
//向遠程機制的後端註冊TaskRunner對象,RegisterWellKnownServiceType方法將完成這一 //操作,並接收下面的參數:
// *內容為TaskRunner對象映象的類型對象
// * 向客戶公布的服務名字
// * 對象的模式:Singleton意味著所有用戶端請求共用一個物件服務;SingleCall意味著 // 每個用戶端請求使用一個新的物件服務。
Console.WriteLine("[i] -- Registering the TaskRunner object");
Type theType = new TaskRunner().GetType();
RemotingConfiguration.RegisterWellKnownServiceType(theType, "TaskServer", WellKnownObjectMode.Singleton);
Console.WriteLine("[i] Task Server started, press to exit...");
Console.ReadLine();
} catch (Exception e) {
Console.WriteLine("[!] An error occured while initialising the TaskServerEngine.");
Console.WriteLine(e);
}
}
}
}
// TaskClient.cs
// 這是一個用戶端應用域,用戶端的任務是建立一個任務對象,並將它提交給任務伺服器
using System;
// 建立與任務伺服器的串連所必需的庫檔案
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using TaskServer;
namespace HappyClientWithTask {
[Serializable()]
// 下面是我們建立的任務
class ClientTask : ITask {
private int num1, num2;
private int result;
public ClientTask(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
public object Run() {
result = num1 * num2;
return (object)result;
}
public string Identify() {
return("I am a multiplication task.");
}
}
public class Client {
public static void Main() {
Console.WriteLine("\nWelcome to the humble, lowly client Application Domain.\n");
ClientTask clientTask = new ClientTask(100,100);
try {
Console.WriteLine("[i] Connecting to TaskServer...");
Console.WriteLine("[i] - Opening TCP Channel");
TcpChannel chan = new TcpChannel();
Console.WriteLine("[i] - Registering the channel");
ChannelServices.RegisterChannel(chan);
Console.WriteLine("[i] Connected to TaskServer");
// 從TaskServer中擷取TaskRunner對象的引用
// Activator類中的GetObject方法需要二個參數:
// * 物件類型
// * 對象的URI位置
Console.WriteLine("[i] Getting a reference to the TaskRunner Object");
TaskRunner taskRunner = (TaskRunner)Activator.GetObject(typeof(TaskServer.TaskRunner),
"tcp://localhost:8085/TaskServer");
if(taskRunner == null) {
Console.WriteLine("[e] Could not locate server.");
Console.WriteLine("[i] Exiting...");
return;
}
Console.WriteLine("[i] We have an object reference!");
// 下面我們將把任務對象傳遞給任務伺服器
Console.WriteLine("\n[i] Submitting our task to the server...");
string response = taskRunner.LoadTask(clientTask);
Console.WriteLine("[i] Server says: " + response);
Console.WriteLine("\n[i] Running the task and awaiting feedback...");
object result = taskRunner.RunTask();
Console.WriteLine("[aaa-uuuum] The Great and Powerful TaskServer Says: " + (int)result);
} catch (Exception e) {
Console.WriteLine("[e] An exception occurred.");
Console.WriteLine(e);
}
}
}
}