# 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
## 一、引言
上一篇博文分享了消息队列(MSMQ)技术来实现分布式应用,在这篇博文继续分享下.NET平台下另一种分布式技术——.NET Remoting。
## 二、.NET Remoting 介绍
## 2.1 .NET Remoting简介
.NET REmoting与MSMQ不同,它不支持离线可得,另外只适合.NET平台的程序进行通信。它提供了一种允许对象通过应用程序域与另一个对象进行交互的框架。.NET 应用程序都在一个主应用程序域中执行的,在一个应用程序域中的代码不能访问另一个应用程序域的数据,然而在某些情况下,我们需要跨应用程序域,与另外的应用程序域进行通信,这时候就可以采用.NET Remoting技术来实现与另一个程序域中的对象进行交互。
## 2.2 .NET Remoting基本原理
.NET Remoting技术是通过通道来实现两个应用程序之间对象的通信的。首先,客户端通过Remoting技术,访问通道来获得服务器端对象,再通过代理解析为客户端对象,也称作透明代理,此时获得客户端对象只是服务器对象的一个引用。这既保证了客户端和服务端有关对象的松散耦合,同时优化了通信的性能。在这个过程中,当客户端通过透明代理来调用远程对象的方法时,此时会将调用封装到一个消息对象中,该消息对象包括远程对象信息,被调用的方法名和参数,然后透明代理会将调用委托给真实代理([RealProxy](http://msdn.microsoft.com/zh-cn/library/system.runtime.remoting.proxies.realproxy.invoke(v=vs.110).aspx)对象)的Invoke方法来生成一个[IMethodCallMessage](http://msdn.microsoft.com/zh-cn/library/system.runtime.remoting.messaging.imethodcallmessage(v=vs.110).aspx),接着通过序列化把这个消息对象序列化成数据流发送到通道,通道会把数据流传送到服务器端。当服务器接收到经过格式化的数据之后,首先从中通过反序列化来还原消息对象,之后在服务器端来激活远程对象,并调用对应的方法,而方法的返回结果过程则是按照之前的方法反向重复一遍,具体的实现原理图如下所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb46cfab9.gif)
## 2.3 .NET Remoting几个重要概念
上面简单介绍了下.NET Remoting实现分布式应用程序的基本原理,这里介绍下在.NET Remoting中涉及的几个重要概念。
1. 远程对象:是运行在服务器端的对象,客户端不能直接调用,由于.NET Remoting传递的对象是以引用的方式,因此所传递的远程对象必须继承MarshalByRefObject类,这个类可以使远程对象在.NET Remoting应用通信中使用,支持对象的跨域边界访问。
2. 远程对象的激活方式:在访问服务器端的一个对象实例之前,必须通过一个名为Activation的进程创建它并进行初始化。这种客户端通过通道来创建远程对象的方式称为对象的激活。在.NET Remoting中,远程对象的激活分为两大类:服务器端激活和客户端激活。
* 服务器端激活,又叫做WellKnow(知名对象)激活模式,为什么称为知名对象激活模式呢?是因为服务应用程序在激活对象实例之前会在一个众所周知的统一资源标示符(URI)上发布这个类型,然后该服务器进行会为此类型配置一个WellKnow对象,并根据指定的端口或地址来发布对象。.NET Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种。
SingleTon模式:此为有状态模式。如果设置为SingleTon激活模式,则.NET Remoting将为所有客户端建立同一个对象实例。当对象处于活动状态时,SingleTon实例会处理所有后来的客户端访问请求,而不管它们是同一个客户端,还是其他客户端。SingleTon实例将在方法调用中一直维护其状态,类似static成员的概念
SingleCall模式:是一种无状态模式。一旦设置为SingleCall模式,则当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例,对象实例的销毁则是由GC自动管理。类似实例成员的概念。
* 客户端激活:与Wellknow模式不同,。NET Remoting在激活每个对象实例的时候,会给每个客户端激活的类型指派一个URI。客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。SingleCall模式与客户端激活模式的区别有:首先,对象实例创建的时间不同。客户端激活方式是客户一旦发出调用请求就实例化,而SingleCall则要等到调用对象方法时再创建。其次,SingleCall模式激活的对象是无状态的,对象声明周期由GC管理,而客户端激活的对象是有状态的,其生命周期可自定义。第三,两种激活模式在服务器端和客户端实现的方法不一样,尤其是在客户端,SingleCall模式由GetObject()来激活,它调用对象默认的构造函数,而客户端激活模式,则通过CreateInstance()来激活,它可以传递参数,所以可以调用自定义的构造函数来创建实例。
3\. 通道:在.NET Remoting中时通过通道来实现两个应用程序域之间对象的通信。.NET Remoting中包括4中通道类型:
1. TcpChannel:Tcp通道使用Tcp协议来跨越.Net Remoting边界来传输序列化的消息流,TcpChannel默认使用二进制格式序列化消息对象,因此具有更高的传输性能,但不提供任何内置的安全功能。
2. HttpChannel:Http通道使用Http协议在客户端和服务器之间发生消息,使其在Internet上穿越防火墙来传输序列化的消息流(这里准确讲不能说穿越,主要是因为防火墙都开放了80端口,所以使用Http协议可以穿过防火墙进行数据的传输,如果防火墙限制了80端口,Http协议也照样不能穿越防火墙)。默认情况下,HttpChannel使用Soap格式序列化消息对象,因此它具有更好的互操作性,并且可以使用Http协议中的加密机制来对消息进行加密来保证安全性。因此,通常在局域网内,我们更多地使用TcpChannel,如果要穿越防火墙,则使用HttpChannel。
3. IpcChannel:进程间通信,只使用同一个系统进程之间的通信,不需要主机名和端口号。而使用Http通道和Tcp通道都要指定主机名和端口号。
4. 自定义通道:自定义的传输通道可以使用任何基本的传输协议来进行通信,如UDP协议、SMTP协议等。
## 三、利用.NET Remoting技术开发分布式应用三部曲
前面详细介绍了.NET Remoting相关内容,下面具体看看如何使用.NET Remoting技术来开发分布式应用程序。开发.NET Remoting应用分三步走。
第一步:创建远程对象,该对象必须继承MarshalByRefObject对象。具体的示例代码如下:
```
1 namespace RemotingObject
2 {
3 // 第一步:创建远程对象
4 // 创建远程对象——必须继承MarshalByRefObject,该类支持对象的跨域边界访问
5 public class MyRemotingObject :MarshalByRefObject
6 {
7 // 用来测试Tcp通道
8 public int AddForTcpTest(int a, int b)
9 {
10 return a + b;
11 }
12
13 // 用来测试Http通道
14 public int MinusForHttpTest(int a, int b)
15 {
16 return a - b;
17 }
18
19 // 用来测试IPC通道
20 public int MultipleForIPCTest(int a, int b)
21 {
22 return a * b;
23 }
24 }
25 }
```
远程对象分别定义3个方法,目的是为了测试3中不同的通道方式的效果。
第二步:创建服务器端,需要添加System.Runtime.Remoting.dll引用,具体实现代码如下所示:
```
1 using System;
2 using System.Runtime.Remoting;
3 using System.Runtime.Remoting.Channels;
4 using System.Runtime.Remoting.Channels.Http;
5 using System.Runtime.Remoting.Channels.Ipc;
6 using System.Runtime.Remoting.Channels.Tcp;
7
8 namespace RemotingServerHost
9 {
10 // 第二步:创建宿主应用程序
11 class Server
12 {
13 static void Main(string[] args)
14 {
15 // 1.创建三种通道
16
17 // 创建Tcp通道,端口号9001
18 TcpChannel tcpChannel = new TcpChannel(9001);
19
20 // 创建Http通道,端口号9002
21 HttpChannel httpChannel = new HttpChannel(9002);
22
23 // 创建IPC通道,端口号9003
24 IpcChannel ipcChannel = new IpcChannel("IpcTest");
25
26 // 2.注册通道
27 ChannelServices.RegisterChannel(tcpChannel, false);
28 ChannelServices.RegisterChannel(httpChannel, false);
29 ChannelServices.RegisterChannel(ipcChannel, false);
30
31 // 打印通道信息
32 // 打印Tcp通道的名称
33 Console.WriteLine("The name of the TcpChannel is {0}", tcpChannel.ChannelName);
34 // 打印Tcp通道的优先级
35 Console.WriteLine("The priority of the TcpChannel is {0}", tcpChannel.ChannelPriority);
36
37 Console.WriteLine("The name of the HttpChannel is {0}", httpChannel.ChannelName);
38 Console.WriteLine("The priority of the httpChannel is {0}", httpChannel.ChannelPriority);
39
40 Console.WriteLine("The name of the IpcChannel is {0}", ipcChannel.ChannelName);
41 Console.WriteLine("The priority of the IpcChannel is {0}", ipcChannel.ChannelPriority);
42
43 // 3\. 注册对象
44 // 注册MyRemotingObject到.NET Remoting运行库中
45 RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject.MyRemotingObject), "MyRemotingObject", WellKnownObjectMode.Singleton);
46 Console.WriteLine("Press any key to exit");
47 Console.ReadLine();
48 }
49 }
50 }
```
第三步:创建客户端程序,具体的实现代码如下所示:
```
1 using RemotingObject;
2 using System;
3
4 namespace RemotingClient
5 {
6 class Client
7 {
8 static void Main(string[] args)
9 {
10 // 使用Tcp通道得到远程对象
11 //TcpChannel tcpChannel = new TcpChannel();
12 //ChannelServices.RegisterChannel(tcpChannel, false);
13 MyRemotingObject proxyobj1 = Activator.GetObject(typeof(MyRemotingObject), "tcp://localhost:9001/MyRemotingObject") as MyRemotingObject;
14 if (proxyobj1 == null)
15 {
16 Console.WriteLine("连接TCP服务器失败");
17 }
18
19 //HttpChannel httpChannel = new HttpChannel();
20 //ChannelServices.RegisterChannel(httpChannel, false);
21 MyRemotingObject proxyobj2 = Activator.GetObject(typeof(MyRemotingObject), "http://localhost:9002/MyRemotingObject") as MyRemotingObject;
22 if (proxyobj2 == null)
23 {
24 Console.WriteLine("连接Http服务器失败");
25 }
26
27 //IpcChannel ipcChannel = new IpcChannel();
28 //ChannelServices.RegisterChannel(ipcChannel, false);
29 MyRemotingObject proxyobj3 = Activator.GetObject(typeof(MyRemotingObject), "ipc://IpcTest/MyRemotingObject") as MyRemotingObject;
30 if (proxyobj3 == null)
31 {
32 Console.WriteLine("连接Ipc服务器失败");
33 }
34 // 输出信息
35 Console.WriteLine("This call object by TcpChannel, 100 + 200 = {0}", proxyobj1.AddForTcpTest(100, 200));
36 Console.WriteLine("This call object by HttpChannel, 100 - 200 = {0}", proxyobj2.MinusForHttpTest(100, 200));
37 Console.WriteLine("This call object by IpcChannel, 100 * 200 = {0}", proxyobj1.MultipleForIPCTest(100, 200));
38 Console.WriteLine("Press any key to exit!");
39 Console.ReadLine();
40 }
41 }
42 }
```
经过上面的三步,我们就完成了这个分布式应用的开发工作,下面测试下该程序是否可以正常运行,首先,运行服务器端,你将看到如下界面:
![](https://box.kancloud.cn/2016-01-23_56a2eb4700c21.png)
在.NET Remoting中,是允许同时创建多个通道的,但是.NET Remoting要求通道的名字必须不同,因为名字是用来标识通道的唯一标识符。但上面代码中,我们并没有指明通道的名字,为什么还可以允许成功呢?从上面图片可知,当我们创建通道时,如果没有为其显式指定通道名,则会使用对应的通道类型作为该通道名,如TcpChannel将会以tcp作为通道名,如果想注册多个Tcp通道则必须显式指定其名字。
下面看看运行客户端所获得的结果,具体客户端运行效果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4710443.png)
## 四、使用配置文件来重写上面的分布式程序
在第三部分中,我们是把服务器的各种通道方式和地址写死在程序中的,这样的实现方式部署起来不方便,下面使用配置文件的方式来配置服务器端的通道类型和服务器地址。远程对象的定义不需要改变,下面直接看服务器端使用配置文件后的实现代码如下所示:
```
1 using System;
2 using System.Runtime.Remoting;
3 using System.Runtime.Remoting.Channels;
4
5 namespace RemotingServerHostByConfig
6 {
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 RemotingConfiguration.Configure("RemotingServerHostByConfig.exe.config", false);
12
13 foreach (var channel in ChannelServices.RegisteredChannels)
14 {
15 // 打印通道的名称
16 Console.WriteLine("The name of the Channel is {0}", channel.ChannelName);
17 // 打印通道的优先级
18 Console.WriteLine("The priority of the Channel is {0}", channel.ChannelPriority);
19 }
20 Console.WriteLine("按任意键退出……");
21 Console.ReadLine();
22 }
23 }
24 }
```
服务端的配置文件的内容为:
```
1 <?xml version="1.0" encoding="utf-8" ?>
2 <!--服务端App.config的内容-->
3 <configuration>
4 <startup>
5 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
6 </startup>
7 <system.runtime.remoting>
8 <application>
9 <service>
10 <wellknown mode="Singleton"
11 type="RemotingObject.MyRemotingObject,RemotingObject"
12 objectUri="MyRemotingObject"/>
13 </service>
14 <channels>
15 <channel port="9001" ref="tcp"/>
16 <channel port="9002" ref="http"/>
17 <channel portName="IpcTest" ref="ipc"/> <!--Ipc通道不需要端口号-->
18 </channels>
19 </application>
20 </system.runtime.remoting>
21 </configuration>
```
此时,客户端程序的实现代码如下所示:
```
1 using RemotingObject;
2 using System;
3 using System.Runtime.Remoting;
4
5 namespace RemotingClientByConfig
6 {
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 //使用HTTP通道得到远程对象
12 RemotingConfiguration.Configure("RemotingClientByConfig.exe.config", false);
13 MyRemotingObject proxyobj1 = new MyRemotingObject();
14 if (proxyobj1 == null)
15 {
16 Console.WriteLine("连接服务器失败");
17 }
18
19 Console.WriteLine("This call object by TcpChannel, 100 + 200 = {0}", proxyobj1.AddForTcpTest(100, 200));
20 Console.WriteLine("This call object by HttpChannel, 100 - 200 = {0}", proxyobj1.MinusForHttpTest(100, 200));
21 Console.WriteLine("This call object by IpcChannel, 100 * 200 = {0}", proxyobj1.MultipleForIPCTest(100, 200));
22 Console.WriteLine("Press any key to exit!");
23 Console.ReadLine();
24 }
25 }
26 }
```
客户端配置文件为:
```
1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <startup>
4 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
5 </startup>
6 <system.runtime.remoting>
7 <application>
8 <client>
9 <wellknown type="RemotingObject.MyRemotingObject,RemotingObject"
10 url="http://localhost:9002/MyRemotingObject" />
11 </client>
12 <channels>
13 <channel ref="tcp" port="0"></channel>
14 <channel ref="http" port="0"></channel>
15 <channel ref="ipc" port="0"></channel>
16 </channels>
17 </application>
18 </system.runtime.remoting>
19 </configuration>
```
使用配置文件修改后的分布式程序的运行结果与前面的运行结果一样,这里就不一一贴图了。
## 五、总结
到这里,.NET Remoting技术的分享就结束了,本文只是对.NET Remoting技术做了一个基本的介绍,如果想深入了解.NET Remoting技术的话,推荐大家可以看看下面的专题[细细品味C#——.Net Remoting专题](http://www.cnblogs.com/xia520pi/archive/2011/11/02/2233371.html)。在下一篇文章中,继续为大家分享另一种分布式技术——Web Service。
本文的示例代码文件下载:[.NETRemotingSample](http://files.cnblogs.com/zhili/NetRemoting.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结