读 《.Net 之美》解析.Net Remoting (应用程序域)-- Part.1

读 《.Net 之美》解析.Net Remoting (应用程序域)-Part1

理解 .Net Remoting

前言:

张子阳老师的文章,总是给自己很大的信心,这个专题基本上以张老师的书为主,我整理书中的主要代码,补充了些自己的理解,希望和大家一起学习:)

背景:

分布式开发是一个重要的方向,前段时间做公司的一个爬虫项目,多个客户端和服务端的交互,使我产生了浓厚的兴趣,WCF、WebService、Socket也是常用 的技术。.Net Remoting 在.NetFrameWork 3.5前是很热的技术,这一章由 (1)应用程序域。(2)传值封送(Marshal by value) (3)传引用封送(Marshal by reference) (4)远程方法回调 (5)客户端和服务端的综合应用

基本概念

应用程序域(Application Domain),域的概念是一个范围,一个中介,既能为程序集(.net 的可执行程序集 .exe)提供托管环境,又能运行在非托管的操作系统进程之内,这个中介就是应用程序域概念的简单阐述。所有的.NET 程序集都运行在应用程序域中。

如果将一个应用程序域视为一个轻量级进程,在一个操作系统进程中可以包含多个, 那这个进程有哪些特性呢?默认应用程序域和隔离性是两个重要的概念。

(1)先加载完可执行程序集->.NET 在当前进程中创建唯一且新的应用程序域,称之为默认应用程序域。

(2)一个进程中包含多个应用程序域,彼此相互独立。

综上所述及推理:

不同的应用程序域可以位于同一进程、同一计算机的不同进程、网络上两台不同计算机的进程中。在不同的应用程序域中是不是有我们关注的对象,对象之间如何交换数据,堆上的内容怎么访问,引出了我们的主角.Net Remoting.

基本操作

.NET 中,将应用程序域封装为System.AppDomain,这个类提供了和应用程序域有关的操作,离不开加载和创建的基本方法,下面的示例代码我们有个了解,在之后实际代码中,会引用就可以了。之后在张老师书中的实例代码,我在调试后,会把整个解决方案打包上传,希望大家批评指正。

//获取当前应用程序域
  //Fun1
            AppDomain currentDomain = AppDomain.CurrentDomain;
  //Fun2
           AppDomain currentDomain2 = Thread.GetDomain();

//获取应用程序域的名称
 currentDomain.FriendlyName;

//创建新的应用程序域
AppDomain newDomain = AppDomain.CreateDomain("NewDomain");

//应用程序域中创建对象
  //Fun1
DemoClass obj=(DemoClass)newDomain.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass");
  //Fun2

1.1 在默认应用程序域中创建对象:大家可以思考下,我们一般用new DemoClass() 的方式和在应用程序域中创建有什么异同或优势,是访问更便捷还是更优化?

 1 //类库
 2   public class DemoClass
 3     {
 4        private int count = 0;
 5        public DemoClass()
 6        {
 7            Console.WriteLine("\n----DemoClass Constructor----");
 8        }
 9
10        public void ShowCount(string name)
11        {
12            count++;
13            Console.WriteLine("{0},the count is {1}.",name,count);
14        }
15
16         /// <summary>
17        /// 打印对象所在的应用程序域
18         /// </summary>
19        public void ShowAppDomain() {
20            AppDomain currentDomain = AppDomain.CurrentDomain;//获取代码所在的应用程序域
21            Console.WriteLine(currentDomain.FriendlyName);//获取应用程序域的名称
22        }
23
24     }
 1 /*控制台测试默认应用程序域中创建对象*/
 2 class Program
 3     {
 4         static void Main(string[] args)
 5         {
 6             Test1();
 7             //Test2();
 8         }
 9
10         /// <summary>
11         /// 在当前AppDomain 中创建一个对象
12         /// </summary>
13         static void Test1() {
14             //获取当前应用程序域-Fun1
15             AppDomain currentDomain = AppDomain.CurrentDomain;
16             //获取当前应用程序域-Fun2
17             AppDomain currentDomain2 = Thread.GetDomain();
18
19             //显示名称
20             Console.WriteLine(currentDomain.FriendlyName);
21
22             //准备创建对象
23             DemoClass obj;
24             //常规创建对象的方式-1
25             //obj = new DemoClass();
26
27             //在默认应用程序域创建对象-2:方式1和2的结果相同。
28             obj = (DemoClass)currentDomain.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass");//强制转换是否改变栈?
29
30       obj.ShowAppDomain();
31             obj.ShowCount("Jimmy");
32             obj.ShowCount("Jimmy");
33
34             Console.Read();
35         }
36 }

运行这段代码,得到的运行结果是

1.2 在新建的应用程序域中创建对象

 1         /// <summary>
 2         /// 在新的应用程序域中创建对象
 3         /// </summary>
 4         static void Test2() {
 5             AppDomain currentDomain = AppDomain.CurrentDomain;
 6             Console.WriteLine(currentDomain.FriendlyName);
 7
 8             //创建一个新的应用程序域,这里是关键代码
 9             AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
10
11             DemoClass obj;//当前默认应用程序域中开辟的栈空间。
12             //在新的应用程序域中创建对象:程序集“ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中的类型“ClassLib.DemoClass”未标记为可序列化。
13             obj=(DemoClass)newDomain.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass"); //新的应用程序域中创建的对象,本地进行反序列化(默认?),还原对象。
14             obj.ShowAppDomain();
15             obj.ShowCount("Jimmy");
16             obj.ShowCount("Jimmy");
17
18             Console.Read();
19         } 

在运行代码后,出现异常,在新的应用程序域中创建对象未标记可序列化,我们分析后,思考

AppDomain.CreateDomain("NewDomain");这句代码之后的对象,是在默认应用程序域还是新的程序域中创建,如果是新的应用程序域,怎么跨程序域取到堆中存储的内容,可序列化的标记是一个什么东东,标记后产生了那些作用。

在思考的同时,先解决问题,看能否得到我们想要的结果

 1     /// <summary>
 2     /// 标记为可序列化,对象从另一个应用程序域中(远程)传递到本地应用程序域。
 3     /// </summary>
 4     [Serializable]
 5     public class DemoClass
 6     {
 7        private int count = 0;
 8        public DemoClass()
 9        {
10            Console.WriteLine("\n----DemoClass Constructor----");
11        }
12
13        public void ShowCount(string name)
14        {
15            count++;
16            Console.WriteLine("{0},the count is {1}.",name,count);
17        }
18
19         /// <summary>
20        /// 打印对象所在的应用程序域
21         /// </summary>
22        public void ShowAppDomain() {
23            AppDomain currentDomain = AppDomain.CurrentDomain;//获取代码所在的应用程序域
24            Console.WriteLine(currentDomain.FriendlyName);//获取应用程序域的名称
25        }
26
27     }

运行这段代码,得到的运行结果是:



我们在来还原一下问题,
currentDomain.FriendlyName//应用程序域的名称:ConsoleApp.vshost.exe,
[Serializable]后发生了作用,可见新的引用程序域中的对象在默认应用程序域中可以"访问"到,这个过程不难想到:先在远程创建对象(非本地默认应用程序域)->将对象序列化*->传递对象*->在本地进行反序列化(这个过程好像是透明的),最后还原成对象(在本地默认应用程序域中),这里的传递过程包含着一些还没有提到的概念,我先标注了*。

我们学到这里好像还是没有体会到DemoClass obj=new DemoClass() 的方式和在应用程序域中创建的方法
DemoClass obj=(DemoClass)newDomain.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass");
有什么异同或优势,是访问更便捷还是更优化,带着问题我们去了解下代理和封送...

1.3 代理和封送1.3.1 代理创建对象过程: 1.new DemoClass(),在托管堆上创建一个对象,并且由obj 变量直接引用。
2.(DemoClass)newDomain.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass"),实际上创建了两个对象,先在NewDomain中第一次创建对象->然后将对象进行复制,序列化->之后进行封送(Marshaling)->接着在默认当前应用程序域(ConsoleApp.exe 客户端),重新创建对象,并还原对象状态,创建对象代理(Proxy).=>我们观察到当代理调用ShowDomain()时,显示的是 ConsoleApp.exe,是因为代理访问的是本地重新创建的对象而非远程对象。

代理这个名词是我联想到生活中的代办公司,我想去申请一家企业营业执照和其他,如果自己去办面临时间和沟通的成本,代办公司经常和政府办事人员打交道,只要我准备好需要的材料和支付必须的费用后,就静候佳音了。

回到项目中, 对于我(相当于客户端),和政府办事部门(想到于服务端)之间要沟通,需要代理帮忙,代理提供和远程对象(本例中是newDomain 中创建的DemoClass)完全相同的接口(此处的接口是指一个类型对外公开的部分,例如属性、方法和事件等).NET 需要在客户端(本例中是ConsoleApp.exe)基于远程对象的类型元数据(Type Metadata)来创建代理,因此客户端必须包含远程对象的类型元数据,元数据简单说类型的名称、公共属性的名称、方法的签名和名称,不包括代码的实现。

封送(Marshal):送有传递的意思,封理解成信息封装。回到项目中,创建好的代理就像是远程对象一样,但代码中不包括方法体,代理仅仅是将自己与某一实际对象绑定,然后把客户程序对自己的请求打包成消息(Message),随后发生给实际对象,请求发送给实际对象的过程,叫做封送(Marshal).

代理的好处:对于客户端来说,远程的服务端对象就好像是在本地一样;          对于服务端(远程的服务端对象)来说,就好像是为器本地对象提供服务。

1.3.2 传值封送、传引用封送

先放示意图给大家
传值封送  

传引用封送(客户端激活对象的方式)

传值封送:位于ConsoleApp.exe 的Obj引用NewDomain 中创建对象时,.NET将NewDomain中对象的状态进行复制,序列化,然后在ConsoleApp.exe中重新创建对象,还原状态,并通过代理进行对对象进行访问.这种跨应用程序域的访问方式叫做传值封送(Mashal by value),有点类似于C#中参数的按值传递。

传引用封送: 传值封送在遇到大对象是效率会降低, 有一种让对象仍然保留在NewDomain,而在客户端创建代理,通过代理来访问远程对象,代理的接口和远程对象相同,当客户端在代理调用方法时:由代理将方法的请求返回给远程对象->远程对象执行方法的请求,最后将结果传给客户端的方式叫做传引用方式(Marshal by reference).

按引用封送的代码示例(一):
 1 //类库
 2
 3  public class DemoClass1:MarshalByRefObject
 4     {
 5         private int count = 0;
 6         public DemoClass1()
 7         {
 8             Console.WriteLine("\n----DemoClass Constructor----");
 9         }
10
11         public void ShowCount(string name)
12         {
13             count++;
14             Console.WriteLine("{0},the count is {1}.", name, count);
15         }
16         //打印对象所在的应用程序域
17         public void ShowAppDomain()
18         {
19             AppDomain currentDomain = AppDomain.CurrentDomain;//获取代码所在的应用程序域
20             Console.WriteLine(currentDomain.FriendlyName);//获取应用程序域的名称
21         }
22
23     }
 1 //控制台应用程序
 2   static void Main(string[] args)
 3         {
 4             Test2();
 5             //Test3();
 6         }
 7
 8         /// <summary>
 9         /// 在新的应用程序域中创建对象
10         /// </summary>
11         static void Test2()
12         {
13             AppDomain currentDomain = AppDomain.CurrentDomain;
14             Console.WriteLine(currentDomain.FriendlyName);
15
16             //创建一个新的应用程序域
17             AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
18
19             DemoClass1 obj;//当前默认应用程序域中开辟的栈空间,注意这里的位置
20             //在新的应用程序域中创建对象:程序集“ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中的类型“ClassLib.DemoClass”未标记为可序列化。
21             obj = (DemoClass1)newDomain.CreateInstanceAndUnwrap("ClassLib1", "ClassLib1.DemoClass1"); //新的应用程序域中创建的对象,本地进行反序列化(默认?),还原对象。
22             obj.ShowAppDomain();
23             obj.ShowCount("Jimmy");
24             obj.ShowCount("Jimmy");//基于引用传递时,对象的状态是保留的,count的值基于变化
25             Console.Read();
26         }
运行这段代码,得到的运行结果是:从结果中分析:有两点值得注意,
(1)obj.ShowAppDomain()//显示NewDomain,说明DemoClass类型实例obj没有传值封送到ConsoleApp.exe,而依然保留在NewDomain中,这个和MarshalByRefObject标记有关,细心的朋友发现此时[Serializable]标记取消了,标记为[Serializable]仅仅说明可以被序列化,而标记上MarshalByRefObject后,它就永远不离开自己的应用程序域,以传引用的方式进行.(2)对象状态保留,连续两次调用ShowCount()方法时,计数器count是累加的.此时我们稍微修改代码,多创建一个DemoClass的实例,继续观察结果...
按引用封送的代码示例(二):
 1         /// <summary>
 2         /// 在新的应用程序域中,创建两次对象
 3         /// </summary>
 4         static void Test3()
 5         {
 6             AppDomain currentDomain = Thread.GetDomain();//AppDomain.CurrentDomain;
 7             Console.WriteLine(currentDomain.FriendlyName);
 8
 9             // create a new appdomain class.
10             AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
11
12             DemoClass1 obj, obj2;
13
14             //在新的应用程序域中创建对象
15             obj=(DemoClass1)newDomain.CreateInstanceAndUnwrap("ClassLib1", "ClassLib1.DemoClass1");
16             obj.ShowAppDomain();
17             obj.ShowCount("Jimmy");
18             obj.ShowCount("Jimmy");
19             //Console.Read();
20
21             obj2 = (DemoClass1)newDomain.CreateInstanceAndUnwrap("ClassLib1", "ClassLib1.DemoClass1");
22             obj2.ShowAppDomain();
23             obj2.ShowCount("Zhang");
24             obj2.ShowCount("Zhang");
25             Console.Read();
26         }
运行这段代码,得到的运行结果是:

分析结果:在NewDomain 中分别创建了两个对象obj1和obj2为客户端服务,且这两个对象各创建了一次的方式(因为只调用一次构造函数),称为客户端激活对象(Client Actived Object,简称CAO).

总结:
通过以上概念和代码的解释,我们渐渐对Remoting有了初步的认识,把握住范围和对象两个关键概念。1.范围:不管两个应用程序域位于同一进程,不同进程,还是不同计算机,只要是跨应用程序域的访问,都属于Remoting的范畴.2.对象:2.1 从请求和提供服务来分为客户端(发出请求)和服务端(提供服务类型).2.2 从应用程序域的角度来看,服务端的应用程序域仅仅是提供了一个服务类型的运行环境,所以本章将Remoting分为三部分.2.2.1 服务端应用程序,提供了服务程序集的运行环境,可以是控制台,窗体,Window服务,IIS的工作者进程.服务程序集对象所在的应用程序域,也称为宿主应用程序域(Host AppDomain).2.2.2 客户端应用程序域(Client AppDomain),向宿主应用程序域发出请求的程序域.2.2.3 服务程序集,其中包含了提供服务的类型,这些类型常继承自MarshalByRefObject,从这句代码中分析obj = (DemoClass1)newDomain.CreateInstanceAndUnwrap("ClassLib1", "ClassLib1.DemoClass1");//DemoClass1所在的ClassLib1程序集.
项目结构:
项目环境: VS2012+控制台应用程序 .Net Framework4.0
网盘路径: http://pan.baidu.com/s/1kTJxBD1 随着文章更新代码
                                        感谢张老师的用心汇聚成的.NET之美,我们相逢于首图的二楼,一路走来,代码的光辉在闪闪发光。                                       本文的链接参考:http://www.cnblogs.com/JimmyZhang/archive/2008/07/26/1252183.html                                       书中的代码如果在调试中出现问题,欢迎大家指正,我再来修改,期待 ing...






时间: 2024-09-28 22:42:22

读 《.Net 之美》解析.Net Remoting (应用程序域)-- Part.1的相关文章

[转]一图读懂JVM架构解析

每个Java开发人员都知道字节码经由JRE(Java运行时环境)执行.但他们或许不知道JRE其实是由Java虚拟机(JVM)实现,JVM分析字节码,解释并执行它.作为开发人员,了解JVM的架构是非常重要的,因为它使我们能够编写出更高效的代码.本文中,我们将深入了解Java中的JVM架构和JVM的各个组件. JVM 虚拟机是物理机的软件实现.Java的设计理念是WORA(Write Once Run Anywhere,一次编写随处运行).编译器将Java文件编译为Java .class文件,然后将

分布式缓存系统 Memcached 状态机之网络数据读取与解析

整个状态机的基本流程如下图所示,后续分析将按该流程来进行. 接上节分解,主线程将接收的连接socket分发给了某工作线程,然后工作线程从任务队列中取出该连接socket的CQ_ITEM,开始处理该连接的所有业务逻辑.这个过程也就是上图中的第一个状态conn_listening. 而工作线程首先进入的状态就是conn_new_cmd,即为这个新的连接做一些准备工作,如清理该连接conn结构的读缓冲区等. 准备状态conn_new_cmd具体分析如下: {  <span style="font

Xml二(解析思想)、

XML解析: * 解析xml可以做: * 如果xml作为配置文件:读取 * 如果xml作为传输文件:写,读 * xml解析思想: * DOM:将文档加载进内存,形成一颗dom树(document对象),将文档的各个组成部分封装为一些对象. * 优点:因为,在内存中会形成dom树,可以对dom树进行增删改查. * 缺点:dom树非常占内存,解析速度慢. Document Element Text Attribute Comment * SAX:逐行读取,基于事件驱动 * 优点:不占内存,速度快 *

读侯凤章的散文

读侯凤章的散文 杨森翔 (一) 侯凤章的散文创作虽然开始于他的学生时代,但正式进入创作是在他走上工作岗位以后.也许是因为生活境遇的关系,他的散文对山区的人生和苦难有着特殊的情结.这可以理解为他理性的"载道".其实,经历了"文革"和新时期改革开放的历史阶段,尤其是改革开放时期中国社会经过政治.经济.文化等方面的历史转折,呈现了错综复杂的文化悖论现象,每个人都必须理性选择.理性面对身心裹挟其中的诸多人生问题.侯凤章作为生于斯.长于斯的山区职业教育工作者,既要无可规避地以

Linux内核解析:进程间通信:管道

管道的定义管道的用途管道的操作管道非法read与write内核实现解析管道通信原理及其亲戚通信解析父子进程通信解析亲缘关系的进程管道通信解析管道的注意事项及其性质管道有以下三条性质shell管道的实现与shell命令进行通信system函数与popen函数区别 管道的定义 管道是第一个广泛应用的进程间通信手段.日常在终端执行shell命令时,会大量用到管道.但管道的缺陷在于只能在有亲缘关系(有共同的祖先)的进程之间使用.为了突破这个限制,后来引入了命名管道. 管道的用途 管道是最早出现的进程间通

xml基础及其解析xml文档

xml基础及其解析xml文档 xml基础及其解析xml文档 xml基础语法 中国特色乱码问题 写xml文件的工具 xml中使用的转义字符 处理指令已经过时 xml的两个重要的功能 xml注释 xml解析Java应用程序读取xml文件的内容 xml解析原理 xml解析工具 DOM4J使用 DOM4J中核心API 将xml文档从磁盘读进内存形成Document对象 读取所有的标签节点 读取所有的属性节点 读取所有的文本节点 解决上面提出的问题 xml基础语法 一个基本的xml构成: <!--vers

[nginx源码分析]配置解析1

整个配置解析主要是函数ngx_init_cycle(&init_cycle)进行处理. ngx_init_cycle(&init_cycle) ngx_time_update()//时间更新,也是在main函数里面讲过 /* * 通过加锁和解锁,来更新如下时间 ngx_cached_time = tp; ngx_cached_http_time.data = p0; ngx_cached_err_log_time.data = p1; ngx_cached_http_log_time.da

读caffe源码(未完待续)

caffe源码阅读杂记 准备 一些参考网页 Neural Networks and Deep Learning TUTORIAL ON DEEP LEARNING FOR VISION Deep Learning Tutorial 知乎-深度学习caffe的代码怎么读 Caffe源码解析 caffe源码结构 官方代码结构doxygen 官方Caffe Tutorial 以C++源码形式配置debug&CPU版的caffe,便于阅读源码与单步调试[参考] 参考官方的文档,先了解某个模块的作用 为了

转发 :QQ游戏百万人同时在线服务器架构实现

QQ游戏于前几日终于突破了百万人同时在线的关口,向着更为远大的目标迈进,这让其它众多传统的棋牌休闲游戏平台黯然失色,相比之下,联众似乎已经根本不是QQ的对手,因为QQ除了这100万的游戏在线人数外,它还拥有3亿多的注册量(当然很多是重复注册的)以及QQ聊天软件900万的同时在线率,我们已经可以预见未来由QQ构建起来的强大棋牌休闲游戏帝国. 服务器程序,其可承受的同时连接数目是有理论峰值的,通过C++中对TSocket的定义类型:word,我们可以判定这个连接理论峰值是65535,也就是说,你的单