读 《.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,这个类提供了和应用程序域有关的操作,离不开加载和创建的基本方法,下面的示例代码我们有个了解,在之后实际代码中,会引用就可以了。之后在张老师书中的实例代码,我在调试后,会把整个解决方案打包上传,希望大家批评指正。
//获取当前应用程序域//Fun1AppDomain currentDomain = AppDomain.CurrentDomain;//Fun2AppDomain 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...
转载于:https://www.cnblogs.com/Frank0400/p/4297173.html
读 《.Net 之美》解析.Net Remoting (应用程序域)-- Part.1相关推荐
- .Net Remoting(应用程序域) - Part.1(转载)
本文来自:http://www.cnblogs.com/JimmyZhang/archive/2008/07/26/1252183.html 作者:张子阳先生 英文名:Jimmy Zhang ...
- 教你如何在Python中读,写和解析CSV文
摘要:在这篇文章中关于"在Python如何阅读CSV文件"中,我们将学习如何读,写和解析的CSV文件的Python. 您知道将表格数据存储到纯文本文件背后的机制是什么吗?答案是CS ...
- java opencsv 乱码_教你如何在Python中读,写和解析CSV文
摘要:在这篇文章中关于"在Python如何阅读CSV文件"中,我们将学习如何读,写和解析的CSV文件的Python. 您知道将表格数据存储到纯文本文件背后的机制是什么吗?答案是CS ...
- c语言text的作用,一个读text文本文件和解析文本的例子(C语言)
一个读text文本文件和解析文本的例子. (1)引入头文件 #include #include #include (2)函数int initConfigFile(const char * pFileN ...
- 第二篇 再读Spring 之 BeanDefinition解析
第二篇 再读Spring 之 BeanDefinition解析 文章目录 第二篇 再读Spring 之 BeanDefinition解析 一.颗粒度问题 二.细说Spring中不同颗粒度对象在解析中的 ...
- Oracle java官网关于可重入读写锁ReentrantReadWriteLock的解析
Oracle java官网关于可重入读写锁ReentrantReadWriteLock的解析 1.[原文链接](https://docs.oracle.com/javase/8/docs/api/ja ...
- python获取未读邮箱数目_python imaplib 获取未读邮件,email解析并按照原名下载附件...
以下所有内容都是基于Python 2 主要内容:imaplib 查看未读邮件 email 下载邮件附件 啰嗦在前面: python 邮件查收功能相关的模块有poplib和imaplib.分别支持POP ...
- PHP程序中的文件锁、互斥锁、读写锁使用技巧解析
文件锁全名叫 advisory file lock, 书中有提及. 这类锁比较常见,例如 mysql, php-fpm 启动之后都会有一个pid文件记录了进程id,这个文件就是文件锁. 这个锁可以防止 ...
- 审查指南关于计算机可读介质,中美专利申请中对“计算机可读介质”的可专利性差异...
法律依据 国际申请WO2013192223A1权利要求书第45条: 45. 一种存储可由一个或多个处理器执行的指令的非暂时性计算机可读介质,执行所述指令以: a)提供多个与多个主类别关联的主类别链接: ...
- 湖畔新知汇 | 一图读懂中美知识产权专题研讨
6月9日,阿里巴巴知识产权研究院齐聚中美法律专家,围绕"商业方法创新需要怎样的保护"."中美两国商业方法保护现状"."知识产权保护制度在理论和实践上的 ...
最新文章
- 实战分享:淘宝Web 3D应用与游戏开发
- php mysql生日提醒_基于AIML的PHP聊天机器人
- SAP UI5中的同步请求和异步请求
- model模型php,thinkphp的model模型的设计经验总结
- 概率占据图(POM)算法理解
- Windows中获取和设置系统日期时间的C程序
- NeHe OpenGL教程 第四十四课:3D光晕
- python爬小说目录_【python入门爬虫】爬取笔趣阁小说
- linux shell 获取系统当前时间 毫秒
- java折半查找(递归版)
- 深圳神州行今日起单向收费 零月租成套餐亮点
- (素材源码)猫猫学IOS(十九)UI之QQ好友列表
- 计算机大赛鼓励语录,比赛加油鼓励经典语录
- php 病案系统,医疗档案管理系统
- 大道至简----多示例学习与注意力机制的巧妙结合
- 物联网的物流企业信息集成综合管理平台,主要有哪些特征?
- python3微信好友个性签名生成云图
- Ultraedit 使用技巧
- arm平台ubuntu环境下telnet安装及启动
- WINDOWS时间服务启动失败的原因
热门文章
- 区块链 共识机制研究的重要定理有哪些
- 中科大计算机考研录取分数线_中科大计算机考研 | 跨考CS上岸经验分享!
- JavaSE基础——GUI编程(AWT)
- Java基础总结04-数组
- 辗转相减法的发展应用-最大比例
- rocketmq 部署启动指南-Docker 版
- spring :cannot be resolved to absolute file path because it does not reside in the file system: jar
- Spring 注解 @Controller,@Service,@Repository,@Component,重定向 与 服务端跳转
- wamp怎么安装mysql服务器_使用WAMPServer套件可安装Apache服务器和MySQL服务器
- Zabbix proxy配置