VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > C#教程 >
  • c#中的远程执行和分布式计算

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  摘要

  远程执行是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);
}
}
}
}

 

  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);



相关教程