.NET分布式应用程序(介绍Web服务和 NET Remoting)

更新时间:2024-01-15 14:06:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

错误!未找到引用源。 分布式应用程序

本章将会介绍企业开发中常用的两种分布式解决方案:Web服务和.NET Remoting。由于开发分布式程序比较复杂,本章的示例在逻辑上非常简单而在结构上又比较完整。读者在阅读之后可以尝试把留言簿的例子使用Web服务或.NET Remoting来重新制作,并在多个服务器上部署。

23.1 分布式应用程序概述

所谓分布式应用程序就是分布在多个物理位置的应用程序。读者可能会问为什么要把一个应用程序分布在不同的地方(不同的服务器)呢?原因有以下几点:

? 承载压力

对于一个大型的系统来说,模块众多,并发量比较大,仅仅使用一个服务器来承载往往会发生压力过大而导致系统瘫痪的情况。对于大型系统来说,我们可以在横向和纵向两方面把系统进行划分,并把这些模块分布到不同的服务器上。如果把一个应用程序再纵向分成Web表现层、业务逻辑层和数据层,那么我们就可以使用三个独立的服务器来承载不同的层

1.Web服务器。承载Web表现层,压力主要在页面请求和资源调用上。 2.应用服务器。承载业务逻辑层,压力主要在业务逻辑计算上。 3.数据库服务器。承载数据层(数据库),压力主要在数据处理上。

横向划分就是按照模块进行划分,经过了这样“切肉丁”式的划分,整个系统的压力就分布到了不同的服务器上,如图23-1所示。

图23-1 “切肉丁”式划分系统

559 第Ⅳ部分 扩展篇

当然,你完全可以把几个模块的业务逻辑层放在同一个物理服务器上,让几个模块共用一个数据库。我们在编写程序的时候还是应该考虑一定的伸缩性,这里的服务器是一个广义的概念。

? 提供服务

如果我们把一个大型系统划分为多个模块,而模块与模块之间又需要通信的话,我们就需要靠一种服务的形式来组织这些关联。使用服务进行功能重用比使用组件进行代码重用更进一层。举例来说,如果在一个系统中的三个模块都需要用到报表功能,一种方法是把报表功能做成一个单独的组件,然后让三个模块都引用这个组件,计算操作由三个模块各自进行;另一种方法是把报表功能做成单独的服务,让这三个模块直接使用这个服务来获取数据,所有的计算操作都在一处进行,很明显后者的方案会比前者好得多。

服务不仅能对内还能对外,如果其他合作伙伴需要使用我们的报表服务,我们又不想直接把所有的信息都公开给它们。在这种情况下组件方式就不是很合理了,通过公开服务并对服务的使用方做授权和验证,那么我们既能保证合作伙伴能得到他们需要的数据,又能保证核心的数据不公开。

本章我们将介绍如何使用Web服务和.NET Remoting来实现分布式应用程序。我们不会过多讨论它们的一些机制和细节,只是通过一些简单的例子让你亲手实现分布式,在这以后你可以进一步查询更多的资料来系统化学习。

Web服务是以消息来通信的,消息在调用方和服务端之间传递,而.NET Remoting是基于对象的,使得对象的方法可以跨网络调用。正因为这个本质的差别,导致Web服务和.NET Remoting(通过.NET Remoting这个单词就可以知道Remoting仅仅是.NET下的解决方案)的特性有很大的不同。

跨平台

和.NET类型结合紧密 效率

通信协议

Web服务 是 否 低 HTTP

.NET Remoting 否 是

HTTP/TCP/IPC

我们看到,.NET Remoting的长处在于能使用TCP协议来进行通信以获得更好的性能,如果我们的系统完全是基于.NET平台而且又在同一个局域网内分布的话,.NET Remoting非常适合;而Web服务由于是使用统一的消息来通信,如果我们的应用程序需要跨平台提供服务而且对效率要求又不是特别高的话,Web服务是一个不错的选择。

你可能还不能完全理解Web服务和.NET Remoting是什么,不要紧,请接着往下看。在VS2005强大IDE的帮助下,分布式应用程序的设计和开发已经不是一件难事了,让我们来感受一下分布式的魅力吧!

23.2 Web服务

23.2.1 创建Web服务

1.首先新建一个空白的解决方案,如图23-2所示。

2.在解决方案资源管理器中右键单击前面建立的解决方案,选择“添加”→“新建网站”命令,如图23-3所示,新建一个ASP.NET Web服务项目。 560 错误!未找到引用源。

图23-2 新建空白解决方案

图23-3 新建ASP.NET Web服务

3.打开App_Code目录下的Service.cs文件,我们看到IDE已经自动为我们创建了一个模板方法。

using System;

using System.Web;

using System.Web.Services;

using System.Web.Services.Protocols;

[WebService(Namespace = \

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Service : System.Web.Services.WebService {

public Service () { }

561 第Ⅳ部分 扩展篇

[WebMethod]

public string HelloWorld() { return \ } }

[WebMethod]属性标注了一个方法,在这里HelloWorld方法仅仅是返回了一个字符串。不需要改动任何代码,直接按CTRL+F5组合键来运行Web服务,如图23-4所示。

可以看到,服务名为Service的服务下有一个HelloWorld方法。此外,系统建议我们修改默认的命名空间。关闭页面,我们来修改服务名和命名空间,把

[WebService(Namespace = \

修改为:

[WebService(Name=\服务的说明\mywebsite.com/\

再次运行服务可以看到服务名、服务说明和命名空间已经成功修改了,如图23-5所示。

图23-4 运行Web服务

图23-5 修改服务名、服务说明和服务命名空间

4.单击HelloWorld链接,如图23-6所示,系统提示我们可以单击“调用”按钮对方法进行测试。 单击“调用”按钮后,方法返回的结果如图23-7所示。

图23-6 对方法进行测试

图23-7 调用Web服务的结果

可以看到,Web服务使用XML形式的数据返回操作结果。

5.在测试了服务没有问题后,我们就要使用ASP.NET应用程序调用Web服务了。前面我们使用VS 2005自带的开发服务器运行Web服务,因此,首先需要使用IIS发布Web服务。在IIS下新建一个虚拟目录,指向Web服务的路径。假设虚拟目录名字为TestWebService,那么使用下面的地址就能访问到这个Web服务。

http://localhost/TestWebService/Service.asmx

562 错误!未找到引用源。

6.右键单击解决方案,新建一个ASP.NET应用程序TestWeb,然后右键单击网站,选择添加Web引用,如图23-8所示。

图23-8 添加Web引用

在URL地址栏输入Web服务的地址或者直接单击浏览至“本地计算机上的Web服务”链接,就可以定位到我们先前建立的Web服务。在Web引用名文本框内输入TestWS,如图23-9所示。

7.单击“添加引用”按钮,如图23-10所示,可以看到IDE自动为我们添加了Web服务的代理。

图23-9 成功找到Web服务

图23-10 IDE创建的Web服务代理

8.修改Default.aspx的Page_Load的事件处理方法来调用Web服务。

protected void Page_Load(object sender, EventArgs e) {

TestWS.MyService service = new TestWS.MyService (); Response.Write(service.HelloWorld()); }

563

第Ⅳ部分 扩展篇

是不是很惊讶,调用Web服务和调用本地类库没有什么区别,IDE已经自动把Web服务的XML定义封装成了方法。在这里TestWS就是添加Web引用的时候输入的Web引用名,MyService就是在Web服务中定义的Name。

[WebService(Name=\服务的说明\

你可能想到了一个问题,现在我们的Web服务是部署在本地进行测试,但是等到发布的时候这个服务可能就部署到其他服务器上了,URL因此也会改变。那么怎么随时修改这个URL呢?现在打开Web.config看看吧,IDE早就为我们想到了这点,在appSettings节点下添加了服务的URL。

\

9.现在可以打开网站看看服务是不是被成功调用了,如图23-11所示,页面显示了“Hello World”字样。

23.2.2 HTTP-GET方式的Web服务

有时你可能希望使用URL直接访问Web服务,HTTP-GET协议的Web服务能满足你的要求。

1.为Web服务创建一个Web.config配置文件。

图23-11 使用ASP.NET网站调用Web服务

在这里我们增加了一个HTTP-GET的协议,使Web服务支持HTTP-GET方式访问。同样,你也可以通过配置文件移除一些协议,需要注意的是,开启HTTP-GET方式的Web服务安全性比较低。现在重新编译一次Web服务。

2.为Web服务新增一个WebMethod属性。

[WebMethod(Description=\计算两个整数的和\public int Add(int a, int b) {

return a + b; }

这个方法接受两个整数参数,返回两个参数的和。注意,在这里我们为方法加了描述。 564 错误!未找到引用源。

3.Web服务已经修改了,那么我们的ASP.NET应用程序是否能自动“感知”到这个修改呢?不能,要使得代理类被更新就需要更新Web引用。可以通过右键单击TestWS文件夹选择“更新Web引用”,如图23-12所示。

4.打开Default.aspx,在页面上加入一个链接来使用HTPP-GET方式调用Web服务的Add方法。

通过读取配置文件获得Web服务的路径,然后在之后加上需要调用的方法名和参数即可。 5.打开页面,单击链接,结果如图23-13所示。

图23-12 更新Web引用

图23-13 使用HTTP-GET方式调用Web服务

23.2.3 异步调用Web服务

假设有一个报表Web服务,其中有一个统计的操作需要花费20秒的时间,难道在调用这个操作的时候页面只能等待而不能去执行其他本地操作吗?当然不是,我们能通过异步调用Web服务来解决这个问题。

1.修改Web服务,再增加一个方法。

[WebMethod(Description = \一个执行时间为2秒的方法\public string LongWork() {

System.Threading.Thread.Sleep(2000); return \编程快乐\}

在这个方法中,我们让线程等待2秒,然后返回执行结果。因此这个方法耗时大约为2秒。 2.编译Web服务,然后更新网站的Web引用。 3.在Default.aspx页面上添加两个按钮。

双击“同步调用”按钮,以同步方式调用Web服务的LongWork操作。

protected void btn_SyncInvoke_Click(object sender, EventArgs e) {

TestWS.MyService service = new TestWS.MyService();

565 第Ⅳ部分 扩展篇

sw = new System.Diagnostics.Stopwatch(); sw.Start();

string s = service.LongWork(); sw.Stop();

Response.Write(\同步调用
\

Response.Write(string.Format(\等待时间:{0}毫秒
\ Response.Write(string.Format(\执行结果:{0}
\}

在这里,我们使用Stopwatch来监视service.LongWork()操作花费的时间。Stopwatch的声明在Page_Load的上面。

public partial class _Default : System.Web.UI.Page {

System.Diagnostics.Stopwatch sw;

protected void Page_Load(object sender, EventArgs e) {

//TestWS.MyService service = new TestWS.MyService(); //Response.Write(service.HelloWorld()); } ?

在这里注释掉Page_Load中原来的操作。

4.启用Default.aspx,单击“同步调用”按钮,结果如图23-14所示。

可以看到,操作执行的时间为2秒左右,调用Web服务方法花费的时间稍微比这个操作长一些。 5.要想启用异步首先需要为页面启用异步,打开Defaut.aspx,把页面的Async属性设置为true。

<%@ Page Language=\Inherits=\

双击“异步调用”按钮,按钮单击事件处理方法为:

{

protected void btn_AsyncInvoke_Click(object sender, EventArgs e) TestWS.MyService service = new TestWS.MyService();

// 当LongWork操作完成后触发service_LongWorkCompleted事件处理方法

service.LongWorkCompleted += new TestWS.LongWorkCompletedEventHandler (service_LongWorkCompleted);

sw = new System.Diagnostics.Stopwatch(); sw.Start(); // 开始异步操作

service.LongWorkAsync(); // 假设按钮的操作会花费1秒的时间

System.Threading.Thread.Sleep(1000); }

可以看到方法LongWork完成后会自动执行service_LongWorkCompleted方法。 566 错误!未找到引用源。

void service_LongWorkCompleted(object sender, TestWS.LongWorkCompletedEventArgs e) {

sw.Stop();

Response.Write(\异步调用
\

Response.Write(string.Format(\等待时间:{0}毫秒
\ Response.Write(string.Format(\执行结果:{0}
\}

通过前面同步调用的例子我们知道,这个方法大约会花费2秒的时间。由于这个方法是异步执行的(不会妨碍当前按钮单击处理事件的其他操作),虽然按钮的单击事件处理方法也花费了1秒的时间,但是总的时间只会是2秒而不是3秒。

简单来说,异步方法不是和当前操作在同一个“跑道”,它们不会发生干扰,总的时间按最长方法的时间而定。

6.启动页面,单击“异步调用”按钮,如图23-15所示。

图23-14 同步调用Web服务操作

图23-15 异步调用Web服务操作

本节简单介绍了如何创建和调用Web服务,并介绍了异步的概念。如果读者有兴趣可以参考其他书籍进一步了解Web服务的机制和原理。下一节将介绍如何创建.NET Remoting分布式应用程序。

23.3 .NET Remoting

23.3.1 第一个.NET Remoting应用程序

所谓.NET Remoting就是跨应用程序域边界调用程序集。如图23-16所示,显示了.NET Remoting应用程序的基本构架。

从图23-16中看到,Remoting服务端承载远程对象,使外界能与之通信,对外的信道可以是HTTP、TCP或者IPC。HTTP方式的信道在跨越防火墙上有优势;TCP方式的信道常用在局域网内通信,速度比HTTP快很多;IPC信道用于同一台机器的进程间通信,通信不占用网络资源,速度又比TCP快很多。因此,这里的服务器是一个广义的概念,对于TCP和HTTP信道,服务器可以是两个独立的物理计算机。

那么,最基本的.NET Remoting应用程序应该由三部分构成: ? 服务端。承载远程对象。

? 远程对象。需要跨应用程序域边界调用的程序集。 ? 客户端。用于调用远程对象。

图23-16 .NET Remoting 应用程序基本构架

567 第Ⅳ部分 扩展篇

远程对象是根本,服务端只是一个载体,那么我们就先来创建一个简单的远程对象:

1.继续使用前面的一个解决方案。右键单击解决方案,选择“添加”→“新建项目”命令,新建一个TestRemoteObject类库项目。

2.把默认的Class1.cs重命名为RemoteObject.cs,打开cs文件,修改代码为:

using System;

namespace RemoteObject

{

public class MyObject : MarshalByRefObject {

public int Add(int a, int b) {

return a + b; } } }

在RemoteObject命名空间下有一个MyObject类,除了继承MarshalByRefObject类使之能跨应用程序域边界被访问之外,和一般的类没有任何区别。

3.右键单击这个类库项目,如图23-17所示。

图23-17 项目属性

我们看到这个项目的程序集名为TestRemoteObject,默认的命名空间为TestRemoteObject。默认的命名空间名字和程序集的名字是一样的,但是在代码中我们的命名空间名字为RemoteObject,和程序集名字不同以便进行区分。

注意:程序集和命名空间是两个不同的概念,一个程序集可以包括几个命名空间,一个命名空间也可以由多个程序集来实现。

在创建了远程对象后就需要创建Remoting服务端来发布这个远程对象了。

4.服务端可以是一个控制台应用程序、Windows应用程序、Windows服务甚至是IIS。为了简单,我们将首先使用控制台应用程序做服务端。在解决方案中新建一个名为TestRemotingConsoleServer的控制台应用程序,然后右键单击项目,选择添加应用,如图23-18所示,添加System.Runtime.Remoting的引用。

5.把Program.cs修改成如下:

using System;

using System.Runtime.Remoting;

568

错误!未找到引用源。

using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp;

namespace TestRemotingConsoleServer {

class Program

{

static void Main(string[] args) {

// 新建一个TCP信道

TcpChannel tc = new TcpChannel(9999); // 注册TCP信道

ChannelServices.RegisterChannel(tc, false); // 注册知名对象

RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject. MyObject), \ // 让控制台不会自动关闭 Console.ReadLine(); } } }

图23-18 添加System.Runtime.Remoting的引用

我们看到,使用.NET Remoting发布远程对象并不复杂,首先需要告知程序使用哪种信道发布远程对象。在这里我们选择TCP信道,并在9999端口通信。然后要告知程序把对象注册为哪种类型,在这里笔者不想详细阐述远程对象的种类和模式,读者只需要理解在这里我们把Remote- Object.MyObject这个类型使用一个固定的名字myObject来发布(因此叫做知名对象),对象的模式是SingleCall,SingleCall模式的对象是无状态的。

最后我们来完成用客户端应用程序调用远程对象。客户端应用程序可以是ASP.NET应用程序、控制台应用程序或者Windows应用程序。那么,我们就直接使用前一节建立的ASP.NET应用程序作

569 第Ⅳ部分 扩展篇

为客户端吧。

6.在TestWeb网站下新建一个RemotingTest.aspx,然后在页面的Page_Load事件处理方法中调用远程对象。

protected void Page_Load(object sender, EventArgs e) {

RemoteObject.MyObject mo = (RemoteObject.MyObject)Activator.GetObject (typeof(RemoteObject.MyObject), \ Response.Write(mo.Add(1, 2)); }

在这里,我们从远程地址tcp://localhost:9999/myObject创建远程对象,并调用了对象的Add()方法。myObject就是在服务端中为知名对象起的名字。

7.编译整个解决方案,IDE提示“找不到命名空间RemoteObject”,这是因为我们的客户端和服务端项目没有引用远程对象类库项目。右键单击服务端项目,选择“添加引用”,在项目页中找到类库项目,单击“确定”按钮,如图23-19所示。

对于客户端项目也同样添加类库的引用,然后重新编译解决方案。

8.现在就能进行测试了。解决方案中的项目如图23-20所示。

图23-19 添加项目引用

图23-20 解决方案中的项目

要让远程调用成功运行,先要启动服务端使之监听端口。如图23-20所示,单击控制台应用程序,项目名自动以粗体标识,表示这是当前项目,按Ctrl+F5组合键直接启动程序。然后再单击TestWeb网站,右键单击RemotingTest.aspx,选择设为起始页,按Ctrl+F5组合键启动网站。

如图23-21所示,页面显示3,成功了!

图23-21 调用远程对象

570 错误!未找到引用源。

注意图23-21所示,在整个过程中需要确保服务端处于运行状态。至此,我们完成了第一个.NET Remoting应用程序。

23.3.2 Remoting的信道

前面提到过,Remoting有多种信道可以选择,这大大增加了我们分布式系统的灵活性。如果希望在广域网通信,可以使用HTTP信道,如果希望在局域网通信取得更好的性能,可以使用TCP信道,如果希望在本机上的不同进程间通信以获得最好的性能,可以使用IPC信道。

下面我们来修改前面的程序,使之使用三种不同的Remoting信道,并且我们要比较三种信道在效率上差多少:

1.首先在远程对象中新增一个方法,使之返回大量的数据。

using System;

namespace RemoteObject {

public class MyObject : MarshalByRefObject {

public int Add(int a, int b) {

return a + b; }

public string[] GetData() {

string[] data = new string[100000]; for (int i = 0; i < data.Length; i++) data[i] = \很大量的数据\ return data; } } }

2.然后修改服务端,使之在三个不同的信道上发布远程对象。

using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels.Ipc;

namespace TestRemotingConsoleServer {

class Program

571 第Ⅳ部分 扩展篇

{

static void Main(string[] args) {

// 新建一个TCP信道

TcpChannel tc = new TcpChannel(9999); // 新建一个HTTP信道

HttpChannel hc = new HttpChannel(8888); // 新建一个IPC信道

IpcChannel ic = new IpcChannel(\ // 注册TCP信道

ChannelServices.RegisterChannel(tc, false); ChannelServices.RegisterChannel(hc, false); ChannelServices.RegisterChannel(ic, false); // 注册知名对象

RemotingConfiguration.RegisterWellKnownServiceType(typeof

(RemoteObject.MyObject), \Mode.SingleCall); // 让控制台不会自动关闭 Console.ReadLine(); } } }

注意,由于是在同一个机器上注册多个信道,需要给每个信道使用不同的端口。对于IPC信道来说,使用一个管道名来区分而不是端口号。为了能更好地测试三者的差别,我们把服务端部署到另外一个服务器上(把EXE文件和DLL文件复制过去)。

3.在RemotingTest.aspx页面上新建三个按钮用于使用不同的信道调用远程对象。

按钮的单击事件处理方法如下:

{

protected void btn_HttpChannel_Click(object sender, EventArgs e)

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start();

RemoteObject.MyObject mo = (RemoteObject.

MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),

\ Response.Write(\信道
\

Response.Write(string.Format(\记录数:{0}条
\ Response.Write(string.Format(\花费时间:{0}毫秒
\}

protected void btn_TcpChannel_Click(object sender, EventArgs e) {

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

572 错误!未找到引用源。

sw.Start();

RemoteObject.MyObject mo = (RemoteObject.MyObject)Activator.GetObject (typeof(RemoteObject.MyObject), \ Response.Write(\信道
\

Response.Write(string.Format(\记录数:{0}条
\ Response.Write(string.Format(\花费时间:{0}毫秒
\}

protected void btn_IpcChannel_Click(object sender, EventArgs e)

{

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start();

RemoteObject.MyObject mo = (RemoteObject.

MyObject)Activator.GetObject(typeof(RemoteObject.MyObject), \

Response.Write(\信道
\

Response.Write(string.Format(\记录数:{0}条
\ Response.Write(string.Format(\花费时间:{0}毫秒
\}

可以看到,使用三种信道调用的代码仅在URL上有区别。在这里我们不但把服务端部署到了远程服务器上,而且在本地也开了一个服务端用于在IPC上注册远程对象。

4.测试结果如图23-22所示。

图23-22 Remoting的三种信道

可以看到在效率上三者有明显的差别。IPC比TCP快是因为它传递数据不经过网络,不占用网络资源。TCP比HTTP快很多是因为默认情况下TCP信道使用二进制序列化,序列化后的数据量很小,而HTTP默认使用SOAP消息进行格式化,基于XML的SOAP消息非常臃肿,因此在传输上会比TCP花费更多的时间。不过不可否认HTTP信道在跨防火墙上的优势,因此使用哪种信道还需要根据自己的需求来选择。

23.3.3 使用配置文件增加灵活性

虽然我们做的Remoting程序可以正常使用,但是整个程序非常不灵活: ? 服务端有关信道、端口等的配置都直接写死在程序里面。

? 客户端设置的远程对象的地址也是写死在程序里面的。

对于客户端的配置不是大问题,因为其实那个URL就是一个字符串。而服务端的配置文件应该怎么做呢?其实一点也不复杂,添加一个app.config然后写入下面的内容:

573

第Ⅳ部分 扩展篇

可以看到配置文件主要由两部分构成:

? 定义远程对象类型的service节点。在这里我们定义了一个知名对象,模式是SingleCall,对象名为myObject。

? 定义信道的channels节点。在这里定义了三个信道,和先前程序方式定义的一样。 特别需要注意的是,这里的type=\,格式是:

type=\命名空间.类型名,程序集名\

对比图23-17看看,现在你知道为什么当时笔者要把命名空间、类型和程序集三者的名字设置不同了吧。那么,怎么让服务端加载配置文件读取Remoting的配置呢?只需要一行代码就行。

RemotingConfiguration.Configure(\; Console.ReadLine();

你可能会奇怪,配置文件是app.config,为什么这里写成了应用程序名.config呢?其实在编译的时候IDE会自动把配置文件进行改名,以免发生冲突,如图23-23所示,可以看到Release目录的 文件。

图23-23 服务端程序release文件夹

真正有用的是加亮的三个文件(分别是远程对象、服务端和配置文件),在部署的时候只需要复制这些文件即可。

虽然改了服务端,但是我们并没有改变通道的端口,因此客户端不需要做任何修改就能直接运行。574 错误!未找到引用源。

如果你希望把URL从程序中分离的话,可以在配置文件中添加几个节点。

然后在代码中调用配置文件读取URL。

RemoteObject.MyObject mo = (RemoteObject.MyObject) Activator.GetObject(typeof(RemoteObject.MyObject), ConfigurationManager.AppSettings[\

其他两个信道的代码差不多,就不列出来了。现在这样就非常灵活了,修改信道、修改端口甚至转移服务端的位置只需要重新调整配置文件即可。

23.3.4 使用接口降低耦合

读者首先要明确一点,客户端调用的远程方法是在服务端执行的。如下,我们在远程对象中增加一个方法。

public void HelloWorld() {

Console.WriteLine(\编程快乐\}

重新编译服务端和客户端,运行客户端可以看到服务端控制台程序上输出了“编程快乐”字样,如图23-24所示。

那么问题就来了,既然远程对象是在服务端执行的,客户端为什么要引用远程对象呢?假设我们的报表系统是使用.NET Remoting开发的,难道要把核心DLL也公布给客户吗(要知道.NET应用程序是很容易被反编译得到“源代码”的)?其实,客户端只需要得到远程怎么实现,客户端并不关心。

那么,怎么构建这个供客户端使用的壳子呢?有两种方法。 ? 直接使用工具比如soapsuds.exe来生成。 ? 使用基于接口的编程方法。

由于篇幅关系,在这里我们仅仅介绍第二种方法的实现:

1.新建一个类库项目ITestRemoteObject,这个类库是前面TestRemoteObject的接口(Interface),因此以字母I开头。

2.打开TestRemoteObject下的RemoteObject.cs,把鼠标放在MyObject类上单击右键,选择“重构”→“提取接口”,如图23-25所示。

单击“全选”按钮选中所有成员,单击“确定”按钮。可以看到TestRemoteObject类库下面多了

图23-24 远程对象在服务端执行

对象的“描述”,知道远程对象的类型以及成员定义,让客户端代码能编译通过即可。具体方法是什么,

575 第Ⅳ部分 扩展篇

一个cs文件,如图23-26所示。

图23-25 提取接口

图23-26 自动生成的接口

IMyObject就是MyObject类对应的接口。打开这个文件可以看到接口其实就是对类成员的定义,没有实际的实现。

using System;

namespace TestRemoteObject {

interface IMyObject {

int Add(int a, int b); string[] GetData(); void HelloWorld(); } }

对这个接口我们要进行一些改动:

? 要让接口能被外部调用,需要把接口加上公有访问修饰符。

? 系统自动以程序集的名字作为命名空间的命名,我们还是改回原来的RemoteObject。

using System;

namespace RemoteObject {

public interface IMyObject {

int Add(int a, int b); string[] GetData(); void HelloWorld(); } }

3.现在这个接口在远程对象文件中,我们需要把它移动到ITestRemoteObject中,直接点击文件,576 错误!未找到引用源。

Ctrl+X(剪切)、CTRL+V(粘贴)即可。

4.回头看MyObject文件:

系统自动让它继承了TestRemoteObject.IMyObject,刚才我们把TestRemoteObject修改成了RemoteObject,现在这里也需要同样修改。

既然让类实现接口,那么就需要让TestRemoteObject项目引用ITestRemoteObject项目。右键单击TestRemoteObject项目,选择添加引用,在项目选项卡中找到ITestRemoteObject项目,单击“确定”按钮即可。

现在两个项目的结构应该如图23-27所示。

你可能会问,接口仅仅是对类的一个定义吗?不仅仅是这样,接口还对类有约束力,如果你修改了接口也一定要修改“实现”。如果你在接口中新加入一个Test()的方法,而不修改“实现”,编译程序会得到编译错误,如图23-28所示。

public class MyObject : MarshalByRefObject, TestRemoteObject.IMyObject

图23-27 基于接口的编程

图23-28 类需要实现接口的成员

5.现在,我们的客户端就可以引用和使用接口,而不是直接引用和使用远程对象了。首先右键单击TestWeb网站,选择属性页。在引用页找到原来的远程对象TestRemoteObject,删除它的引用,并添加ITestRemoteObject的引用,如图23-29所示。

图23-29 修改网站项目的引用

577 第Ⅳ部分 扩展篇

查找替换Remoting.aspx.cs中的所有RemoteObject. MyObject为RemoteObject.IMyObject,比如:

RemoteObject.IMyObject mo = (RemoteObject.IMyObject)Activator. GetObject(typeof(RemoteObject.IMyObject), ConfigurationManager. AppSettings[\mo.HelloWorld();

6.重新编译解决方案,先后运行服务端和客户端,效果和原来的没有什么不同。但是,这样的方式更灵活了,或者说耦合更低了。为什么这样说呢?因为,现在如果希望在服务端的实现中做什么改动的话,不需要重新编译和部署客户端程序。

23.3.5 使用Windows服务承载远程对象

现在的程序看似很完美,但是要想真正应用还有一些问题。我们的服务端是一个控制台应用程序,如果在服务器上需要有10个Remoting的服务端,那么我们服务器重启动后也需要重启动这10个程序吗?读者可能会说可以把它们加入开始菜单的启动中让程序自动启动。但是你有没有想过,在登录到服务器进行维护的时候很容易不小心把控制台程序关闭了,而且关闭之后还不知道。

要想解决这个问题就需要使用一种后台式的程序来作为服务端,Windows服务正好可以满足这个要求,而且还可以设置Windows服务自动启动。使用VS 2005创建.NET的Windows服务非常简单,下面我们一起来实现Windows服务版本的Remoting服务端。

1.创建一个新的Windows服务项目TestRemotingService,如图23-30所示。

图23-30 创建新的Windows服务项目

2.打开Service1代码视图,找到OnStart部分,加入代码。

protected override void OnStart(string[] args) {

System.Runtime.Remoting.RemotingConfiguration.Configure(AppDomain.CurrentDomain. BaseDirectory + \ }

578

错误!未找到引用源。

这句代码实现在Windows服务启动的时候从Windows服务安装目录所在的配置文件加载Remoting配置,然后把先前控制台服务端的配置文件复制过来。

现在这个Windows服务是Remoting的服务端,因此也别忘记添加对TestRemoteObject远程对象的引用。

3.切换到Service1的设计视图,在空白处右键单击,然后选择“添加安装程序”选项。如图23-31所示。

图23-31 添加服务安装程序

4.打开系统自动生成的ProjectInstaller.cs,如图23-32所示,可以看到页面上有两个组件。

图23-32 服务安装程序

单击serviceProcessInstaller1组件,观察属性窗口,如图23-33所示。

在这里我们把Account属性设置为LocalSystem,作为服务的账户类型。然后单击serviceInstaller1组件,观察属性窗口,如图23-34所示。

图23-33 ServiceProcessInstaller组件

图23-34 ServiceInstaller组件

在这里可以设置服务友好名、服务的描述、服务名和启动方式。只需要把StartType设置为Automatic,服务就能在系统重新启动后自动启动。

579 第Ⅳ部分 扩展篇

5.现在就可以安装服务了,单击“开始”菜单→“所有程序”→Microsoft Visual Studio 2005→Visual Studio Tools→“Visual Studio 2005命令行提示”,如图23-35所示,使用installutil程序来安装Windows服务。

图23-35 使用installutil工具安装Windows服务

如果你觉得输入exe所在路径太麻烦,可以直接打开文件夹把exe文件拖入命令行窗口。卸载服务使用–u参数。

installutil -u Windows服务exe所在路径

6.执行“我的电脑右键”→“管理”→“服务和应用程序”→“服务”命令。如图23-36所示,可以在列表中找到我们的服务。

图23-36 服务已经安装成功

查看这个服务的属性,如图23-37所示。

图23-37 Windows服务的属性

7.如果程序写的没有什么问题的话(其实我们只写了一行代码),服务应该能正常启动,然后可580 错误!未找到引用源。

以打开网站进行测试。

注意:由于安全问题,必须为Windows服务指定一个有效账户(Account=User)才能使用IPC信道,在这里就不详细叙述了。

除了使用Windows服务承载远程对象外,还可以使用IIS。不过需要注意,使用IIS承载远程对象只能在HTTP信道上通信,好处在于可以使用IIS来进行安全管理。需要说的是,HTTP方式的Remoting效率非常低(甚至不如Web Service),因此不推荐。具体实现IIS部署Remoting的方法在这里就不说明了。

23.3.6 异步操作

在介绍Web服务的时候,我们介绍了异步调用Web服务的操作,在这里我们将介绍如何异步调用远程对象的方法。

1.首先在远程对象中加一个耗时2秒的方法。

public string LongWork() {

System.Threading.Thread.Sleep(2000); return \编程快乐\}

别忘记同时更新接口。

using System;

namespace RemoteObject {

public interface IMyObject {

int Add(int a, int b); string[] GetData(); void HelloWorld(); string LongWork(); } }

2.在客户端RemotingTest.aspx上添加一个按钮,按钮的单击事件处理方法如下:

protected void btn_AsyncInvoke_Click(object sender, EventArgs e) {

sw = new System.Diagnostics.Stopwatch(); sw.Start();

RemoteObject.IMyObject mo = (RemoteObject.IMyObject)

Activator.GetObject(typeof(RemoteObject.IMyObject), ConfigurationManager. AppSettings[\

MyDelegate md = new MyDelegate(mo.LongWork);

581 第Ⅳ部分 扩展篇

AsyncCallback ac = new AsyncCallback(this.CallBack); IAsyncResult Iar = md.BeginInvoke(ac, null); System.Threading.Thread.Sleep(1000); }

在这里使用了两个私有变量,一个是用于Stopwatch,另外一个是方法的代理,在Page_Load上 添加。

private delegate string MyDelegate(); private System.Diagnostics.Stopwatch sw;

在调用了异步方法后线程休息了1秒,异步方法完成之后会调用回调方法。

public void CallBack(IAsyncResult Iar) {

if (Iar.IsCompleted) {

Response.Write(\异步调用
\

Response.Write(string.Format(\花费时间:{0}毫秒
\ ElapsedMilliseconds)); }

3.由于方法是异步调用的,方法执行2秒,我们当前的按钮单击处理事件占用1秒,总共占用的时间也是2秒,如图23-38所示。

}

图23-38 异步调用方法

异步调用远程对象的方法和异步调用本地对象的方法其实差不多,在这里就不详述了。

23.4 回顾与总结

本章我们介绍了分布式应用程序的基本概念及使用Web服务和.NET Remoting开发分布式应用程序的基本方法。

23.1节介绍了分布式构架的优势以及Web服务和.NET Remoting各自适用的场合。

23.2节介绍如何创建和使用Web服务,以及使用HTTP-GET方式和异步方式调用Web服务。 23.3节首先介绍了如何创建一个基本的.NET Remoting应用程序。然后介绍了使用配置文件和接口方式增加程序灵活性,之后介绍了如何使用Windows服务承载服务端。最后,我们介绍了使用异步方式调用远程方法。

本章只能说是开启了分布式开发的大门,微软.NET框架3.0版本已经引入了全新的WCF(Windows Communication Foundation),使用WCF,我们开发面向服务的分布式应用程序就更简单了,有兴趣的读者可以继续研究WCF的相关内容。 582

本文来源:https://www.bwwdw.com/article/lxio.html

Top