关于MarshalByRefObject的解释
首先了解一下不同应用程序域中的对象的通信方式有两种:
一种是跨应用程序域边界传输对象副本
一种是使用代理交换消息。
简单来讲,继承此类的对象可以跨越应用程序域边界被引用,甚至被远程引用.
当一个对象需要长途跋涉到另一个环境中时,需要将其marshal成一个可以传输的形态(比如在.NET Remoting中对象将被打包成一个serializable的ObjRef实例——这个ByRef就是指ObjRef这种形态);同理,当打包以后传输到目标地点,还要执行unmarshal的操作将其还原为内存中的对象
你实际上只拥有对这个对象的一个远程引用,虽然你可以调用它的方法,但实际上这些操作都是发生在远程的(就是前面讲过的过程)
别外解释一下WebService,
Web services 使用 XML 来编解码数据,并使用 SOAP(通信协议)来传输数据。大家可以发现WebService代理类的MarshalByRefObject对象的。
remoting 是MarshByReference的,可以传变量的引用,直接对服务器对象操作。速度快,适合intranet(企业内部互联网)。
webservice 是MarshByValue的,必须传对象的值。速度慢,可以过FIREWALL,配置比较简单,适合internet(因特网)。
概括的说Remoting与Web Services的区别是:
(1)既支持TCP信道又支持HTTP信道,传输速度快
(2)即可传输XML的SOAP包又可传输二进制流,效率高
(3)Remoteing主要用于C/S结构项目
(4)不一定要依赖IIS服务器
一般来说,remoting是和平台相关的,需要客户和服务器都是.NET,但可配置特性比较好,可以自定义协议。web service可以做到跨平台通信,但必须采用SOAP协议。
出处:https://blog.csdn.net/weixin_33907511/article/details/93344855
=======================================================================================
C# MarshalByRefObject 和Serializable的区别
这两种方式的类一般都是用于远程传输时使用。
marshalbyrefobject是通过引用传递serializable是通过值传递,现在就来分析下什么是引用传递,什么是值传递。
理解这个对Remoting或者webservice的认识是很重要的。
marshalbyrefobject(引用)本机或者是服务器上的其实都是同一个实例,只不过是服务器创建后你在本地使用了那个对象而已。比如说A类继承了marshalbyrefobject那么A类由服务器创建实例了,客户端都可以使用这个实例了。
现在我们假设A类有一个方法叫着A,Function返回值为一个string类型这个方法有一系列的操作。客户端在调用这个方法的时候只得到服务器返回的一个值,那个一系列的操作都将在服务器完成,这就是所谓的馊客服端。
Serializable(值类型)这个就不同了,假定我们刚才的那个A类的Funciton方法需要一个B类作为参数,B是一个可序列化的类,也就是类的定义上面加了[Serializable()],如果没加那么这个方法将会报错。我们通过一个remoting的例子来解释一下
先写一个继承marshalbyrefobject的类
public class HelloServer : MarshalByRefObject
{
public HelloServer()
{ Console.WriteLine("HelloServer activated"); }
public String HelloUserMethod(User user)
{
string title;
if (user.Male)
title = "先生";
else
title = "女士";
Console.WriteLine( "Server Hello.HelloMethod : 你好,{0}{1}", user.Name,title);
return "你好," + user.Name + title;
}
}
再写一个可序列化的类
[Serializable]
public class User
{
public User(string name,bool male)
{
this.name = name;
this.male = male;
}
string name="";
bool male=true;
public string Name
{
get{return name;}
set{name = value;}
}
public bool Male
{
get{return male;}
set{male = value;}
}
}
现在我们将在服务端和客户端使用它们。
服务端如下:
public class Server
{
public static int Main(string [] args)
{
TcpChannel chan1 = new TcpChannel(8085);
HttpChannel chan2 = new HttpChannel(8086);
ChannelServices.RegisterChannel(chan1);
ChannelServices.RegisterChannel(chan2);
RemotingConfiguration.RegisterWellKnownServiceType (typeof(HelloServer), "SayHello", WellKnownObjectMode.Singleton); //创建类的实例
System.Console.WriteLine("Press Enter key to exit");
System.Console.ReadLine();
return 0;
}
客户端如下:
public class Client
{
public static void Main(string[] args)
{
//使用HTTP通道得到远程对象
HttpChannel chan2 = new HttpChannel();
ChannelServices.RegisterChannel(chan2);
HelloServer obj1 = (HelloServer)Activator.GetObject(
typeof(RemotingSamples.HelloServer),
"http://localhost:8086/SayHello");//创建类的实例
if (obj1 == null)
{
System.Console.WriteLine(
"Could not locate HTTP server");
}
Console.WriteLine(
"Client1 TCP HelloUserMethod {0}",
obj1.HelloUserMethod(new User("张生",true))); //将类作为参数
(将User作为参数必须是serializable) }
}
}
出处:https://blog.csdn.net/CNHK1225/article/details/50351433
=======================================================================================
MarshalByRefObject 陷阱
近日在做一个项目,涉及到跨 AppDomain 的处理,在使用 MarshalByRefObject 对象时遇到并解决了一些“诡异”的问题,故在此总结一下,与大家分享,也留日后备忘。
跨域传递数据时,只有两类数据可以传递:可序列化类型、MarshalByRefObject 派生类型。前者包括基本类型、使用了 SerializeAttribute 的类型、实现了ISerializable 接口的类型。在跨域传递数据时,可序列化类型的数据将通过序列化传递数据副本;MarshalByRefObject 派生类型则只是在请求数据AppDomain创建一个指向目标AppDomain中真实对象的透明代理。
2 {
3 Console.WriteLine("当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
4 AppDomain domain = AppDomain.CreateDomain("RemoteDomain");
5
6 domain.DoCallBack(delegate()
7 {
8 //在远程域创建数据对象;
9 AppDomain.CurrentDomain.SetData("DT", new DataObject());
10 });
11
12 //获取远程域中创建的数据对象;
13 DataObject dt = (DataObject)domain.GetData("DT");//dt实际是 System.Runtime.Remoting.Proxies.__TransparentProxy
14 dt.DoSomething();
15
16 Console.ReadLine();
17 }
18 }
19
20 publicclass DataObject : MarshalByRefObject
21 {
22 publicvoid DoSomething()
23 {
24 Console.WriteLine("当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
25 }
26 }
以上的规则看起来似乎很简单,进行跨域传递对象似乎也没什么特别的地方。但如果真的认为毫无特别之处,那你就真的错了。下面我把我曾犯过的一些错误:
- 错误1:清空上面代码中对创建的 DataObject 的所有引用,再执行 GC.Collect() 操作强制执行垃圾回收,将会销毁创建的 DataObject 对象。
实际上并不会产生预期的 DataObject 在执行 GC.Collect() 之后立即被回收的效果。我们将上面的代码修改如下:
2 {
3 publicstaticvoid Main(string[] args)
4 {
5 Console.WriteLine("当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
6 AppDomain domain = AppDomain.CreateDomain("RemoteDomain");
7
8 domain.DoCallBack(delegate()
9 {
10 //在远程域创建数据对象;
11 AppDomain.CurrentDomain.SetData("DT", new DataObject());
12 });
13
14 //获取远程域中创建的数据对象;
15 DataObject dt = (DataObject)domain.GetData("DT");//dt实际是 System.Runtime.Remoting.Proxies.__TransparentProxy
16 dt.DoSomething();
17
18 domain.SetData("DT", null);
19 dt =null;
20 GC.Collect();
21
22 Console.ReadLine();
23 }
24 }
25
26 publicclass DataObject : MarshalByRefObject
27 {
28 public DataObject()
29 {
30 Console.WriteLine("创建了 DataOjbect…… 当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
31 }
32
33 ~DataObject()
34 {
35 Console.WriteLine("销毁 DataObject ……当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
36 }
37
38 publicvoid DoSomething()
39 {
40 Console.WriteLine("当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
41 }
42 }
除非你立即执行 AppDomain.Unload(domain); 卸载远程域,才会立即销毁 DataObject,此时能够看到析构函数中的输出。
为什么呢? DataObject 似乎已经没有任何引用了,难道不是吗?这实际上还有引用:domain 中进行跨域通讯的 Remoting 基础结构在持有 DataObject ,该结构是负责将请求数据的远程AppDomain 中的透明代理的调用请求转发给真实的对象的,MarshalByRefOjbect 通过生命期服务 LifeService 控制该结构的生命周期。DataObject 对象之所以未被销毁是因为该基础结构的生命周期尚未过期。默认情况下,如果不再有任何调用操作后大约15分钟将销毁该结构,DataOjbect 才可以被垃圾回收。对生命周期的控制请参考 MSDN 关于.NET Remoting 的生命期服务相关内容。
- 错误2:通过透明代理访问一个基本类型的属性(如 int)或者可序列化的对象类型的属性不会产生对真实对象所在的远程域的远程调用。
2 {
3 // ID;
4 publicint _id;
5 ///<summary>
6 /// ID;
7 /// 读写属性;
8 ///</summary>
9 publicint ID
10 {
11 get { return _id; }
12 set { _id = value; }
13 }
14
15 // data1;
16 private InnerData1 _data1;
17 ///<summary>
18 /// data1;
19 /// 读写属性;
20 ///</summary>
21 public InnerData1 Data1
22 {
23 get { return _data1; }
24 set { _data1 = value; }
25 }
26
27 // data2;
28 private InnerData2 _data2;
29 ///<summary>
30 /// data2;
31 /// 读写属性;
32 ///</summary>
33 public InnerData2 Data2
34 {
35 get { return _data2; }
36 set { _data2 = value; }
37 }
38
39
40 public DataObject()
41 {
42 Console.WriteLine("创建了 DataOjbect…… 当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
43 }
44
45 ~DataObject()
46 {
47 Console.WriteLine("销毁 DataObject ……当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
48 }
49
50 publicvoid DoSomething()
51 {
52 Console.WriteLine("当前域:{0}", AppDomain.CurrentDomain.FriendlyName);
53 }
54 }
55
56 [Serializable]
57 publicclass InnerData1
58 {
59 publicstring Data { get; set; }
60 }
61
62 publicclass InnerData2 : MarshalByRefObject
63 {
64 publicstring Data { get; set; }
65 }
这也是一个陷阱。实际上,无论属性的类型是什么,通过透明代理对对象的任何访问,包括通过 GET/SET 访问器访问属性、访问公开的成员、调用方法等,都会产生跨域调用,即使属性/成员的类型是可序列化的,透明代理也不会保存其属性值的副本,如上面的修改后的 DataObject 的所有的属性,包括 public int _id; 成员。运行以上的测试代码很容易验证,不做详细叙述了。
知晓这一点所具备的指导意义是:当频繁访问一个大数据量而不变化的序列化类型的属性时,更好的做法是一次访问后在本地做缓存,而不要直接通过透明代理访问属性,因为跨域调用时频繁地对大数据进行序列化和反序列化将会消耗你宝贵的计算资源。
跨域操作表面简单,而底层实际是 .NET Remoting 实现的,这其中的机制是比较复杂的。透明代理虽然给我们带来了巨大的便利,屏蔽了大量的复杂的底层细节的同时,也带来了让人容易在不了解底层机制的情形下误用而产生难以察觉的 BUG 的风险。当你需要进行跨 AppDomain 操作是,深入了解 .NET Remoting 机制对于提升系统的健壮性是很重要的。
出处:https://www.cnblogs.com/haiq/articles/1758060.html
=======================================================================================
对MarshalByRefObject的解释
问:
打扰一下,请问MarshalByRefObject中的"Marshal"应该怎样理解?
回复:
按照package的意思理解——当一个对象需要长途跋涉到另一个环境中时,需要将其marshal成一个可以传输的形态(比如在.NET Remoting中对象将被打包成一个serializable的ObjRef实例——这个ByRef就是指ObjRef这种形态);同理,当打包以后传输到目标地点,还要执行unmarshal的操作将其还原为内存中的对象。:)
问:
谢谢!
MarshalByRefObject是不是可以这样理解:对被引用的对象进行Marshal。如果按照package的意思理解,那package的过程是怎样的?
MSDN上这样讲:
MarshalByRefObject是通过使用代理交换消息来跨应用程序域边界进行通讯的对象的基类.
MarshalByRefObject对象在本地应用程序域的边界内可直接访问。远程应用程序域中的应用程序首次访问MarshalByRefObject时,会向该远程应用程序传递代理。对该代理后面的调用将封送回驻留在本地应用程序域中的对象。
在Marshal中,上面所说的代理是什么?有什么用?
MSDN上还讲到:
当跨应用程序域边界使用类型时,类型必须是从MarshalByRefObject继承的,而且由于对象的成员在创建它们的应用程序域之外无法使用,所以不得复制对象的状态。
既然对象的状态不能传递过去,那传递这个对象又有何意义?
第一次去理解MarshalByRefObject,有的问题可能提的比较肤浅,请您指点。
回复:
MarshalByRefObject是所有可以在AppDomain边界外部访问的对象的基类,重心不是marshal,而是object,即object that could be marshaled by reference,也就是可以通过Ref(实际上是ObjRef对象)的机制进行“封送”(MSDN中文版对marshal一词的翻译)的对象。封送的行为是由代理来做的,这里说的代理就是我文章中讲过的.NET Remoting的真实代理(即RemotingProxy)。真实代理不是有一个Invoke()方法吗?当你透过对一个MBRO的透明代理访问该对象的方法时,透明代理将把基于堆栈的方法调用转换为方法调用消息(IMethodCallMessage)并转发给真实代理(在Remoting的场合中也即RemotingProxy),而RemotingProxy的任务就是把对象封送并连同方法调用消息一起转发给远程应用程序域;到达目的地以后的操作类似:远程应用程序域中的监听方当收到发来的方法调用消息时,先取出封送好的ObjRef(这个对象里面保存着发来调用的那个对象!),将其结封(unmarshal)为本地的对象,并获得其透明代理,然后就可以把方法调用消息在转换回基于堆栈的调用发送给这个对象。
对象是在本地维护的,但是方法可以在远程调用。你比如说一个web应用程序,你是通过本地的浏览器远程访问这个应用程序,但是应用程序的状态不会由你的浏览器负责(所以你只是在访问这个应用程序提供给你的功能而已,你并没于拥有应用程序本身,包括其所有数据),你只是发送一个个的请求,服务器告诉你处理的结果。在Remoting中 也是一样,当你获得一个远程对象的时候,你实际上只拥有对这个对象的一个远程引用,虽然你可以调用它的方法,但实际上这些操作都是发生在远程的(就是前面 讲过的过程),你只是传入了一些参数,得到了一个结果,但对象的状态还是在远程维护的(换句话说,对象本身也就是对象的所有状态并没有被往返传递,传递的 只是传入传出的参数——当然,如果参数是一个MBRO的话,还是传递对象被封送的引用)。
也许应该给你准备一个好理解的例子……你就会豁然开朗了。:)
问:
我这样的理解对不对?
一般的对象与从MarshalByRefObject继承的对象区别是:
一般的对象只能在本地应用程序域之内被引用,而MarshalByRefObject对象可以跨越应用程序域边界被引用,甚至被远程引用。
回复:
Exactly! 当对象跨出AppDomain边界的时候,实际上只是它的一个引用(ObjRef)。你比如说吧:
public class LocalObject
{
public void CallRemoteObject(MarshalByRefObject mbro)
{
Console.WriteLine(mbro.ToString());
}
}
当传入一个在本地创建的mbro对象时,ToString()方法是直接发送给对象的;而当mbro是通过Remoting创建的远程对象的话,实际上它只是一个包含有已经marshal好的ObjRef的透明代理,ObjRef里面有什么?对象实例的URI!所以当你调用这个远程对象时,相当于向这个远程端口(tcp://remoteServer/xxxx.rem)发送方法调用消息而已。只不过透明代理隐藏了对象位置的概念,而RemotingProxy真实代理则是实际上处理远程方法调用和对象封送的中枢对象。
出处:http://www.blogjava.net/killme2008/archive/2007/03/28/107037.html
=======================================================================================