首页  ·  知识 ·  编程语言
一步一步学Remoting之一:从简单开始
Jasonlee's Blog  http://www.jasonlee.cn/article.asp?id=77  .NET  编辑:dezai  图片来源:网络
一、Remoting的优缺点? 优点: 1、能让我们进行分布式开发 2、Tcp通道的Remoting速度非常快 3、虽然是远程的,但是非常接近于本地调

一、Remoting的优缺点?
优点:
1、能让我们进行分布式开发
2、Tcp通道的Remoting速度非常快
3、虽然是远程的,但是非常接近于本地调用对象
4、可以做到保持对象的状态
5、没有应用程序限制,可以是控制台,winform,iis,windows服务承载远程对象
缺点:
1、非标准的应用因此有平台限制
2、脱离iis的话需要有自己的安全机制

二、Remoting和Web服务的区别?
    ASP.NET Web 服务基础结构通过将 SOAP 消息映射到方法调用,为 Web 服务提供了简单的 API。通过提供一种非常简单的编程模型(基于将 SOAP 消息交换映射到方法调用),它实现了此机制。ASP.NET Web 服务的客户端不需要了解用于创建它们的平台、对象模型或编程语言。而服务也不需要了解向它们发送消息的客户端。唯一的要求是:双方都要认可正在创建和使用的 SOAP 消息的格式,该格式是由使用 WSDL 和 XML 架构 (XSD) 表示的 Web 服务合约定义来定义的。 
    . NET Remoting 为分布式对象提供了一个基础结构。它使用既灵活又可扩展的管线向远程进程提供 .NET 的完全对象语义。ASP.NET Web 服务基于消息传递提供非常简单的编程模型,而 .NET Remoting 提供较为复杂的功能,包括支持通过值或引用传递对象、回调,以及多对象激活和生命周期管理策略等。要使用 .NET Remoting,客户端需要了解所有这些详细信息,简而言之,需要使用 .NET 建立客户端。.NET Remoting 管线还支持 SOAP 消息,但必须注意这并没有改变其对客户端的要求。如果 Remoting 端点提供 .NET 专用的对象语义,不管是否通过 SOAP,客户端必须理解它们。

三、最简单的Remoting的例子
1、远程对象:
建立类库项目:RemoteObject

using System;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        public int Add(int a,int b)
        {
            return a+b;
        }
    }
}

2、服务端
建立控制台项目:RemoteServer

using System;
using System.Runtime.Remoting;

namespace RemoteServer
{
    class MyServer
    {
        [STAThread]
        static void Main(string[] args)
        {
            RemotingConfiguration.Configure("RemoteServer.exe.config");
            Console.ReadLine();
        }
    }
}

建立配置文件:app.config

   
       
           
                                    mode="Singleton" />
           

           
               
           

       

   

3、客户端:
建立控制台项目:RemoteClient

using System;

namespace RemoteClient
{
    class MyClient
    {
        [STAThread]
        static void Main(string[] args)
        {
            RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            Console.WriteLine(app.Add(1,2));
            Console.ReadLine();
        }
    }
}

建立配置文件:app.config

 
 
 

4、测试
在最后编译的时候会发现编译报错:
1、找不到app.Add()
2、找不到RemoteObject
这是因为客户端RemoteClient没有添加RemoteObject的引用,编译器并不知道远程对象存在哪些成员所以报错,添加引用以后vs.net会在客户端也保存一个dll,可能大家会问这样如果对远程对象的修改是不是会很麻烦?其实不麻烦,对项目编译一次vs.net会重新复制dll。
然后直接运行客户端会出现“目标主机拒绝”的异常,也说明了通道没有打开
运行服务端再运行客户端出现“找不到程序集RemoteObject”!回头想想可以发现我们并在服务端对RemoteObject添加引用,编译的时候通过是因为这个时候并没有用到远程对象,大家可能不理解运行服务端的时候也通过?这是因为没有这个时候还没有激活远程对象。理所当然,对服务端要添加引用远程对象,毕竟我们的对象是要靠远程承载的。
现在再先后运行服务端程序和客户端程序,客户端程序显示3,测试成功。

四、结束语
我们通过一个简单的例子实现了最简单的remoting,对其实质没有做任何介绍,我想通过例子入门才是最简单的

 

 

 

远程对象的激活模式分服务端激活和客户端激活两种,(也就是对象分服务端激活对象或者说是知名对象和客户端激活对象两种)先看看msdn怎么描述服务端激活的:

服务器激活的对象是其生存期由服务器直接控制的对象。服务器应用程序域只有在客户端在对象上进行方法调用时才创建这些对象,而不会在客户端调用 new 或 Activator.GetObject 时创建这些对象;这节省了仅为创建实例而进行的一次网络往返过程。客户端请求服务器激活的类型实例时,只在客户端应用程序域中创建一个代理。然而,这也意味着当您使用默认实现时,只允许对服务器激活的类型使用默认构造函数。若要发布其实例将使用带参数的特定构造函数创建的类型,可以使用客户端激活或者动态地发布您的特定实例。
服务器激活的对象有两种激活模式(或 WellKnownObjectMode 值):Singleton 和 SingleCall。

Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求都将由该实例来提供服务。由于 Singleton 类型具有关联的默认生存期,即使任何时候都不会有一个以上的可用实例,客户端也不会总接收到对可远程处理的类的同一实例的引用。

SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务,即使上一个实例尚未被系统回收。SingleCall 类型不参与生存期租约系统。

首先对于服务端激活的两种模式来做一个试验,我们把远程对象做如下的修改:

using System;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        private int i=0;
        public int Add(int a,int b)
        {
            return a+b;
        }

        public int Count()
        {
            return ++i;
        }
    }
}

对客户端做以下修改:
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            Console.WriteLine(app.Count());
            Console.ReadLine();
第一次打开客户端的时候显示1,第二次打开的时候显示2,类推……由此验证了Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求都将由该实例来提供服务。

把服务器端的config修改一下:                     mode="SingleCall" />
(这里注意大小写,大写的C)
再重新运行服务端和客户端,打开多个客户端发现始终显示1。由此验证了SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务。

下面再说一下客户端的激活模式,msdn中这么写:

客户端激活的对象是其生存期由调用应用程序域控制的对象,正如对象对于客户端是本地对象时对象的生存期由调用应用程序域控制一样。对于客户端激活,当客户端试图创建服务器对象的实例时发生一个到服务器的往返过程,而客户端代理是使用对象引用 (ObjRef) 创建的,该对象引用是从在服务器上创建远程对象返回时获取的。每当客户端创建客户端激活的类型的实例时,该实例都将只服务于该特定客户端中的特定引用,直到其租约到期并回收其内存为止。如果调用应用程序域创建两个远程类型的新实例,每个客户端引用都将只调用从其中返回引用的服务器应用程序域中的特定实例。

理解一下,可以归纳出
1、客户端激活的时间是在客户端请求的时候,而服务端激活远程对象的时间是在调用对象方法的时候

远程对象修改如下:
using System;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        private int i=0;

        public MyObject()
        {
            Console.WriteLine("激活");
        }

        public int Add(int a,int b)
        {
            return a+b;
        }

        public int Count()
        {
            return ++i;
        }
    }
}

服务端配置文件:

   
       
           
               
           

           
               
           

       

   

客户端程序:
using System;

namespace RemoteClient
{
    class MyClient
    {
        [STAThread]
        static void Main(string[] args)
        {
            //RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
            //Console.WriteLine(app.Count());
            Console.ReadLine();
        }
    }
}

客户端配置文件:

 
 
 

(这里的uri按照服务端配置文件中application元素定义的RemoteServer来写)

运行程序可以看到,在客户端启动的时候服务端就输出了“激活”,我们再转回知名模式进行测试发现只有运行了方法才会在服务端输出“激活”。

2、客户端激活可以调用自定义的构造方法,而不像服务端激活只能使用默认的构造方法

把客户端代码修改如下:
RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),new object[]{10},new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
            Console.WriteLine(app.Count());
这里看到我们在CreateInstance方法的第二个参数中提供了10作为构造方法的参数。在服务端激活模式我们不能这么做。

远程对象构造方法修改如下:

public MyObject(int k)
        {
            this.i=k;
            Console.WriteLine("激活");
        }
毫无疑问,我们运行客户端发现输出的是11而不是1了。

3、通过上面的例子,我们运行多个客户端发现出现的永远是11,因此,客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。

总结:
1、Remoting支持两种远程对象:知名的和客户激活的。知名的远程对象使用了uri作为标识,客户程序使用这个uri来访问那些远程对象,也正式为什么称作知名的原因。对知名的对象来说2种使用模式:SingleCall和Singleton,对于前者每次调用都会新建对象,因此对象是无状态的。对于后者,对象只被创建一次,所有客户共享对象状态,因此对象是有状态的。另外一种客户端激活对象使用类的类型来激活,uri再后台被动态创建,并且返回给客户程序。客户激活对象是有状态的。
2、对于Singleton对象来说需要考虑伸缩性,Singleton对象不能在多个服务器上被部署,如果要跨服务器就不能使用Singleton了。

备注:个人习惯原因,在我的例子中服务端的配置都是用config文件的,客户端的配置都是基本用程序方式的
使用配置文件的优点:无需重新编译就可以配置通道和远程对象,编写的代码量比较少
使用程序定制的优点:可以获得运行期间的信息,对程序调试有利。


对客户端做以下修改:第一次打开客户端的时候显示1,第二次打开的时候显示2,类推……由此验证了把服务器端的config修改一下: (这里注意大小写,大写的C)再重新运行服务端和客户端,打开多个客户端发现始终显示1。由此验证了SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务。下面再说一下客户端的激活模式,msdn中这么写:客户端激活的对象是其生存期由调用应用程序域控制的对象,正如对象对于客户端是本地对象时对象的生存期由调用应用程序域控制一样。对于客户端激活,当客户端试图创建服务器对象的实例时发生一个到服务器的往返过程,而客户端代理是使用对象引用 (ObjRef) 创建的,该对象引用是从在服务器上创建远程对象返回时获取的。每当客户端创建客户端激活的类型的实例时,该实例都将只服务于该特定客户端中的特定引用,直到其租约到期并回收其内存为止。如果调用应用程序域创建两个远程类型的新实例,每个客户端引用都将只调用从其中返回引用的服务器应用程序域中的特定实例。理解一下,可以归纳出1、客户端激活的时间是在客户端请求的时候,而服务端激活远程对象的时间是在调用对象方法的时候远程对象修改如下:服务端配置文件:客户端程序:客户端配置文件:(这里的uri按照服务端配置文件中application元素定义的RemoteServer来写)运行程序可以看到,在客户端启动的时候服务端就输出了“激活”,我们再转回知名模式进行测试发现只有运行了方法才会在服务端输出“激活”。2、客户端激活可以调用自定义的构造方法,而不像服务端激活只能使用默认的构造方法把客户端代码修改如下:
 

using System;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        private int i=0;
        public int Add(int a,int b)
        {
            return a+b;
        }

        public int Count()
        {
            return ++i;
        }
    }
}

对客户端做以下修改:
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            Console.WriteLine(app.Count());
            Console.ReadLine();
第一次打开客户端的时候显示1,第二次打开的时候显示2,类推……由此验证了Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求都将由该实例来提供服务。

把服务器端的config修改一下:                     mode="SingleCall" />
(这里注意大小写,大写的C)
再重新运行服务端和客户端,打开多个客户端发现始终显示1。由此验证了SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务。

下面再说一下客户端的激活模式,msdn中这么写:

客户端激活的对象是其生存期由调用应用程序域控制的对象,正如对象对于客户端是本地对象时对象的生存期由调用应用程序域控制一样。对于客户端激活,当客户端试图创建服务器对象的实例时发生一个到服务器的往返过程,而客户端代理是使用对象引用 (ObjRef) 创建的,该对象引用是从在服务器上创建远程对象返回时获取的。每当客户端创建客户端激活的类型的实例时,该实例都将只服务于该特定客户端中的特定引用,直到其租约到期并回收其内存为止。如果调用应用程序域创建两个远程类型的新实例,每个客户端引用都将只调用从其中返回引用的服务器应用程序域中的特定实例。

理解一下,可以归纳出
1、客户端激活的时间是在客户端请求的时候,而服务端激活远程对象的时间是在调用对象方法的时候

远程对象修改如下:
using System;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        private int i=0;

        public MyObject()
        {
            Console.WriteLine("激活");
        }

        public int Add(int a,int b)
        {
            return a+b;
        }

        public int Count()
        {
            return ++i;
        }
    }
}

服务端配置文件:

   
       
           
               
           

           
               
           

       

   

客户端程序:
using System;

namespace RemoteClient
{
    class MyClient
    {
        [STAThread]
        static void Main(string[] args)
        {
            //RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
            //Console.WriteLine(app.Count());
            Console.ReadLine();
        }
    }
}

客户端配置文件:

 
 
 

(这里的uri按照服务端配置文件中application元素定义的RemoteServer来写)

运行程序可以看到,在客户端启动的时候服务端就输出了“激活”,我们再转回知名模式进行测试发现只有运行了方法才会在服务端输出“激活”。

2、客户端激活可以调用自定义的构造方法,而不像服务端激活只能使用默认的构造方法

把客户端代码修改如下:
RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),new object[]{10},new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
            Console.WriteLine(app.Count());
这里看到我们在CreateInstance方法的第二个参数中提供了10作为构造方法的参数。在服务端激活模式我们不能这么做。

远程对象构造方法修改如下:

public MyObject(int k)
        {
            this.i=k;
            Console.WriteLine("激活");
        }
毫无疑问,我们运行客户端发现输出的是11而不是1了。

3、通过上面的例子,我们运行多个客户端发现出现的永远是11,因此,客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。

总结:
1、Remoting支持两种远程对象:知名的和客户激活的。知名的远程对象使用了uri作为标识,客户程序使用这个uri来访问那些远程对象,也正式为什么称作知名的原因。对知名的对象来说2种使用模式:SingleCall和Singleton,对于前者每次调用都会新建对象,因此对象是无状态的。对于后者,对象只被创建一次,所有客户共享对象状态,因此对象是有状态的。另外一种客户端激活对象使用类的类型来激活,uri再后台被动态创建,并且返回给客户程序。客户激活对象是有状态的。
2、对于Singleton对象来说需要考虑伸缩性,Singleton对象不能在多个服务器上被部署,如果要跨服务器就不能使用Singleton了。

备注:个人习惯原因,在我的例子中服务端的配置都是用config文件的,客户端的配置都是基本用程序方式的
使用配置文件的优点:无需重新编译就可以配置通道和远程对象,编写的代码量比较少
使用程序定制的优点:可以获得运行期间的信息,对程序调试有利。


 

一步一步学Remoting之三:复杂对象

这里说的复杂对象是比较复杂的类的实例,比如说我们在应用中经常使用的DataSet,我们自己的类等,通常我们会给远程的对象传递一些自己的类,或者要求对象返回处理的结果,这个时候通常也就是需要远程对象有状态,上次我们说了几种激活模式提到说只有客户端激活和Singleton是有状态的,而客户端激活和Singleton区别在于Singleton是共享对象的。因此我们可以选择符合自己条件的激活方式:
                        状态      拥有各自实例
Singleton              有            无
SingleCall              无            有
客户端激活              有            有
 
在这里,我们先演示自定义类的传入传出:
先说一个概念:MBV就是按值编码,对象存储在数据流中,用于在网络另外一端创建对象副本。MBR就是按引用编组,在客户机上创建代理,远程对象创建ObjRef实例,实例被串行化传递。

我们先来修改一下远程对象:
using System;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        private MBV _mbv;
        private MBR _mbr;
        public int Add(int a,int b)
        {
            return a+b;
        }

        public MBV GetMBV()
        {
            return new MBV(100);
        }

        public MBR GetMBR()
        {
            return new MBR(200);
        }

        public void SetMBV(MBV mbv)
        {
            this._mbv=mbv;
        }

        public int UseMBV()
        {
            return this._mbv.Data;
        }

        public void SetMBR(MBR mbr)
        {
            this._mbr=mbr;
        }

        public int UseMBR()
        {
            return this._mbr.Data;
        }
    }

    [Serializable]
    public class MBV
    {
        private int _data;
        public MBV(int data)
        {
            this._data=data;
        }
        public int Data
        {
            get
            {
                return this._data;
            }
            set
            {
                this._data=value;
            }
        }
    }

    public class MBR:MarshalByRefObject
    {
        private int _data;
        public MBR(int data)
        {
            this._data=data;
        }
        public int Data
        {
            get
            {
                return this._data;
            }
            set
            {
                this._data=value;
            }
        }
    }
}
Get方法用来从服务器返回对象,Set方法用于传递对象到服务器,Use方法用来测试远程对象的状态是否得到了保存。

我们先来测试一下客户端激活模式:(服务器端的设置就不说了)
    RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
            RemoteObject.MBV mbv=app.GetMBV();
            Console.WriteLine(mbv.Data);
            RemoteObject.MBR mbr=app.GetMBR();
            Console.WriteLine(mbr.Data);
            mbv=new RemoteObject.MBV(100);
            app.SetMBV(mbv);
            Console.WriteLine(app.UseMBV());
            //mbr=new RemoteObject.MBR(200);
            //app.SetMBR(mbr);
            //Console.WriteLine(app.UseMBR());
            Console.ReadLine();
依次显示:100,200,100
前面2个100,200说明我们得到了服务器端返回的对象(分别是MBV和MBR方式的),后面一个100说明我们客户端建立了一个MBV的对象传递给了服务器,因为客户端激活模式是有状态的所以我们能使用这个对象从而输出100,最后我们注释了几行,当打开注释运行后出现异常“由于安全限制,无法访问类型 System.Runtime.Remoting.ObjRef。”这个在【通道】一节中会讲到原因。

好了,我们再来测试一下Singleton(别忘记修改客户端配置文件中的URI哦)
RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            //RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
后面的语句省略,运行后同样出现100,200,100-》Singleton也是能保存状态的。
SingleCall呢?修改一下服务端的Config再来一次,在“Console.WriteLine(app.UseMBV());”出现了“未将对象引用设置到对象的实例。”因为服务端没有能够保存远程对象的状态,当然出错。

再看一下.net内置的一些复杂对象,比如DataSet,可能传入传出DataSet和DataTable在应用中比较普遍,一些不可序列话的类我们不能直接传递,比如DataRow等,要传递的时候可以考虑放入DataTable容器中。

远程对象修改如下:
using System;
using System.Data;

namespace RemoteObject
{
    public class MyObject:MarshalByRefObject
    {
        public DataSet Method(DataSet ds)
        {
            DataTable dt=ds.Tables[0];
            foreach(DataRow dr in dt.Rows)
            {
                dr["test"]=dr["test"]+"_ok";
            }
            return ds;
        }
    }
}
客户端修改如下:
    RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            DataSet ds=new DataSet();
            DataTable dt=new DataTable();
            dt.Columns.Add(new DataColumn("test",typeof(System.String)));
            DataRow dr=dt.NewRow();
            dr["test"]="data";
            dt.Rows.Add(dr);
            ds.Tables.Add(dt);
            ds=app.Method(ds);
            Console.WriteLine(ds.Tables[0].Rows[0]["test"].ToString());
            Console.ReadLine();
运行后发现输出data_ok了。在这里不管用哪种模式来激活都会得到data_ok,因为我们并没有要求远程对象来保存状态。

总结:

所有必须跨越应用程序域的本地对象都必须按数值来传递,并且应该用 [serializable] 自定义属性作标记,否则它们必须实现 ISerializable 接口。对象作为参数传递时,框架将该对象序列化并传输到目标应用程序域,对象将在该目标应用程序域中被重新构造。无法序列化的本地对象将不能传递到其他应用程序域中,因而也不能远程处理。通过从 MarshalByRefObject 导出对象,可以使任一对象变为远程对象。当某个客户端激活一个远程对象时,它将接收到该远程对象的代理。对该代理的所有操作都被适当地重新定向,使远程处理基础结构能够正确截取和转发调用。尽管这种重新定向对性能有一些影响,但 JIT 编译器和执行引擎 (EE) 已经优化,可以在代理和远程对象驻留在同一个应用程序域中时,防止不必要的性能损失。如果代理和远程对象不在同一个应用程序域中,则堆栈中的所有方法调用参数会被转换为消息并被传输到远程应用程序域,这些消息将在该远程应用程序域中被转换为原来的堆栈帧,同时该方法调用也会被调用。从方法调用中返回结果时也使用同一过程。


 

一步一步学Remoting之四:承载方式

 

在实际的应用中我们通常只会选择用windows服务和iis来承载远程对象。选择windows服务的原因是能自启动服务,服务器重启后不需要再去考虑启动service。选择iis的理由是我们能使用集成验证等一些iis的特性。
在msdn中可以找到相关文章:

http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/BuildSucApp/BSAAsecmodsecmod29.mspx

http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpguide/html/cpconRemotingExampleHostingInIIS.asp

可能大家会觉得这个过程将是一个复杂的过程,其实不然,下面说一下实现方法,步骤非常少。

先来建立远程对象
using System; 
using System.Data;
using System.Data.SqlClient;

namespace RemoteObject 

    public class MyObject:MarshalByRefObject 
    { 
        public DataSet GetData()
        {
            SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["strconn"]);
            SqlDataAdapter da=new SqlDataAdapter("select * from UBI_ProvinceMaster",conn);
            DataSet ds=new DataSet();
            da.Fill(ds);
            return ds;
        }
    }

客户端仍然是一个控制台来进行测试:
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            DataTable dt=app.GetData().Tables[0];
            foreach(DataRow dr in dt.Rows)
            {
                Console.WriteLine(dr["iPrMId"]+" "+dr["vPrMName"]);
            }
            Console.ReadLine();
服务端配置文件:

   
           
   

   
       
           
                                    mode="SingleCall" />
           

           
               
           

       

   

运行程序,我们得到的是一个省市的列表:


一、windows服务承载
用vs.net制作一个windows服务的过程基本不超过10个步骤,所以我们不需要害怕。
1、建立一个新的windows服务项目RemoteServer1
2、打开Service1代码视图,找到OnStart部分,加入代码 System.Runtime.Remoting.RemotingConfiguration.Configure(AppDomain.CurrentDomain.BaseDirectory + "RemoteServer1.exe.config"); (不要遗漏AppDomain.CurrentDomain.BaseDirectory + )
config和控制台方式的config是一样的,我们让这个windows服务做的仅仅是从config文件读出配置信息进行配置通道。别忘记添加配置文件。
3、切换到设计视图,右键-添加安装程序
4、切换到新生成的ProjectInstaller.cs设计视图,找到serviceProcessInstaller1对Account属性设置为LocalSystem,对serviceInstaller1的ServiceName属性设置为RemoteServer1(服务的名字),StartType属性设置为Automatic(系统启动的时候自动启动服务)
5、别忘记对添加RemoteObject的引用
6、建立一个新的安装项目RemoteServerSetup(我们为刚才那个服务建立一个安装项目)
7、右键-添加-项目输出-主输出-选择RemoteService1-确定
8、右键-视图-自定义操作-自定义操作上右键-添加自定义操作-打开应用程序文件夹-选择刚才那个主输出-确定
9、重新生成这个安装项目-右键-安装
10、在服务管理器中(我的电脑-右键-管理-服务和应用程序-服务)找到RemoteServer1服务,启动服务
现在就可以打开客户端测试了!

一些FAQ:
1、启动服务的时候系统说了类似“服务什么都没有做,服务已经被停止”表示什么?
表示windows服务出错了,一般是服务的程序有问题,检查服务做了什么?在我们这个程序中仅仅添加了一行代码,一般不会出现这个错误。
2、运行客户端出现“服务器无响应”?
先检查windows服务配置文件是不是正确设置了激活方式和激活对象,客户端服务端端口号是否统一?
3、运行客户端出现“无法找到程序集”?
检查windows服务配置文件是否正确配置了激活对象的类型和uri?服务是否添加了远程对象引用?
4、远程对象类中有用到System.Configuration.ConfigurationSettings.AppSettings["strconn"],但是远程对象并没有配置文件,它从哪里读取这个config的?
因为远程对象不是独立存在的,它是被windows服务承载的,因此它从windows服务的配置文件中读取一些配置信息,远程对象本生不需要配置文件。
5、安装的时候是不是要卸载服务?
不需要,安装程序会 停止服务端-》卸载服务-》安装服务
6、在正式使用的时候怎么部署我们的系统?
如果客户端是程序仅仅只要把安装项目下面3个文件传到服务器进行安装,配置好config文件(比如连接字符串),开启服务即可。如果客户端是网站,同样把服务在服务器安装,配置好config文件(比如连接字符串),开启服务,最后把网站传到web服务器(可能和service不是同一个服务器)。
7、部署的时候需要传远程对象dll吗?
不需要,可以看到安装项目中已经自动存在了这个dll。
8、这样的系统有什么特点?
一个web服务器,多个service服务器,多个sqlservice服务器,web服务器负担比较小,所有的逻辑代码都分布到不同的service服务器上面。

最后说一个测试的tip:
如果我们远程调用对象进行测试程序非常麻烦,我们需要这么做
修改了远程对象-》重新编译安装程序-》在自己机器重新安装服务-》启动服务-》查看结果
其实可以这么做:
1、修改远程对象中的连接数据库字符串,由于不是远程对象了,我们必须从本地读取连接字符串,比如上列我们直接修改为:
SqlConnection conn=new SqlConnection("server=(local);uid=sa;pwd=;database=UBISOFT");
2、修改客户端代码,直接实例化远程对象
//RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
            RemoteObject.MyObject app = new RemoteObject.MyObject();

等到正式部署的时候我们还原数据库连接字符串从config文件中读取,还原远程对象从远程读取即可。

如果对windows服务还不是很清楚,请看以下文章:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbwlkwalkthroughcreatingwindowsserviceapplication.asp

http://www.cnblogs.com/lovecherry/archive/2005/03/25/125527.html

这里来说一下iis承载方式,顺便简单说一下remoting的通道和【复杂对象】中的遗留问题。

首先明确一点:iis来承载的话只能是http通道方式的。

我们来建立一个web项目,比如叫remoting,删除项目中的所有webform,把远程对象dll-RemoteObject.dll复制到项目的dll文件夹下面,然后打开web.config进行服务端设置:


   
           
   

   
       
           
                                    mode="SingleCall" />
           
   
           
               
           
   
       

   

来分析一下这个config:
1、可能大家还不是很理解type属性,其实type属性分两部分<命名空间.类名>,<程序集>
2、objectURi是用来表示对象的uri的,到时候我们用这个uri来连接到服务端
3、我们需要为uri指定soap或者rem后缀

要进行测试其实很简单,我们在浏览器输入:http://localhost/remoting/MyObject.soap?wsdl
进行测试,如果发生问题基本就是配置文件的问题或者对象dll没有正确复制到dll目录

接下来修改一下客户端的配置文件就可以了,主要是修改地址。


 
 http://localhost/remoting/MyObject.soap"/>
 

iis承载方式默认是80端口,我们不需要在端口上做任何设置。还需要注意到的是iis方式,我们使用这样的格式作为地址:
http://ip地址/虚拟目录/远程对象.soap

运行了客户端以后如果我们的数据量比较大的话,就算是本机我们也能感受到延迟,比tcp方式延迟厉害很多很多,其实http方式的remoting效率比webservice还要差,具体选择http方式的remoting还是webservice还是要看我们是不是对对象的状态有需求。

iis的部署也是自动启动服务的,还有一个优点就是可以结合iis的windows身份认证,这个参照一些iis的配置文章,这里就不说了。

下面还是要来看一下两种【通道】:
    默认情况下,HTTP 通道使用 SOAP 格式化程序,因此,如果客户端需要通过 Internet 访问对象,则可以使用 HTTP 通道。由于这种方法使用 HTTP,所以允许客户端通过防火墙远程访问 .NET 对象。将这些对象集成在 IIS 中,即可将其配置为 Web 服务对象。随后,客户端就可以读取这些对象的 WSDL 文件,以便使用 SOAP 与 Remoting 对象通信。
    默认情况下,TCP 通道使用二进制格式化程序。此格式化程序以二进制格式进行数据的序列化,并使用原始套接字在网络中传送数据。如果对象部署在受防火墙保护的封闭环境中,则此方法是理想的选择。该方法使用套接字在对象之间传递二进制数据,因此性能更好。由于它使用 TCP 通道来提供对象,因此具有在封闭环境中开销较小的优点。由于防火墙和配置问题,此方法不能在 Internet 上使用。

因此我们也需要更根据自己的需求来选择通道!看看remoting有这么多可以选择的方式:选择激活模式,选择通道,选择承载方式,如此多的选择给了我们灵活的同时也增加了理解remoting的难度。

最后说一下前面的遗留问题,为什么会发生这个安全异常?
http://www.cnblogs.com/lovecherry/archive/2005/05/20/159335.html

msdn说:
依赖于运行时类型验证的远程处理系统必须反序列化一个远程流,然后才能开始使用它,未经授权的客户端可能会试图利用反序列化这一时机。为了免受这种攻击,.NET 远程处理提供了两个自动反序列化级别:Low 和 Full。Low(默认值)防止反序列化攻击的方式是,在反序列化时,只处理与最基本的远程处理功能关联的类型,如自动反序列化远程处理基础结构类型、有限的系统实现类型集和基本的自定义类型集。Full 反序列化级别支持远程处理在所有情况下支持的所有自动反序列化类型。

我们首先来修改服务端的配置文件:


   
       
           
                                    mode="Singleton" />
           

           
               
               
                   
                   
                   
                   

           

       

   


然后客户端还要用程序进行调整:
若要使用配置文件设置反序列化级别,必须显式指定 元素的 typeFilterLevel 属性。虽然这通常是在服务器端指定的,但您还必须为注册来侦听回调的客户端上的任何信道指定这一属性,以控制其反序列化级别-msdn

添加了System.Runtime.Remoting.dll引用之后


using System;
using System.Collections;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;

在程序前面加上:


BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
            BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
            IDictionary props = new Hashtable();
            props["port"] = 0;
            TcpChannel channel = new TcpChannel(props,clientProvider,serverProvider);
            ChannelServices.RegisterChannel(channel);

这样就可以了,注意:如果在同一个机器上面测试端口号应设为不同于服务器端设置的端口号,推荐设置为0。

.NET Remoting 自身不提供安全模型。然而,通过将远程对象驻留在 ASP.NET 中并使用 HTTP 通道进行通信,远程对象可以使用 IIS 和 ASP.NET 提供的基本安全服务。比较而言,TCP 通道和自定义的主机可执行文件能够提供更高的性能,但这种组合不提供内置的安全功能。

• 若要对客户端进行身份验证,请使用 HTTP 通道,在 ASP.NET 中驻留对象,以及在 IIS 中禁用匿名访问。
 
• 如果您不担心客户端身份验证问题,请使用 TCP 通道,它可以提供更高的性能。
 
• 如果您使用 TCP 通道,请使用 IPSec 保护客户端和服务器之间的通信通道。使用 SSL 来保护 HTTP 通道。
 
• 如果您需要对远程资源进行受信任的调用,请将组件驻留在 Windows 服务中,而不是驻留在控制台应用程序中。
 
• 始终不要向 Internet 公开远程对象。在这种情况下,请使用 Web 服务。
应该仅在 Intranet 中使用 .NET Remoting。应该使用内部方式从 Web 应用程序访问对象。即使对象驻留在 ASP.NET 中,也不要向 Internet 客户端公开它们,因为客户端必须是 .NET 客户端。

最后,让我们来看一篇msdn有关remoting安全的文章:
http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/BuildSucApp/BSAAsecmod11.mspx
说到这里大家可能对remoting的一些基本知识稍微优点概念了,后续文章会继续不断强化这些概念!

 

本文作者:Jasonlee's Blog 来源:http://www.jasonlee.cn/article.asp?id=77
CIO之家 www.ciozj.com 微信公众号:imciow
   
免责声明:本站转载此文章旨在分享信息,不代表对其内容的完全认同。文章来源已尽可能注明,若涉及版权问题,请及时与我们联系,我们将积极配合处理。同时,我们无法对文章内容的真实性、准确性及完整性进行完全保证,对于因文章内容而产生的任何后果,本账号不承担法律责任。转载仅出于传播目的,读者应自行对内容进行核实与判断。请谨慎参考文章信息,一切责任由读者自行承担。
延伸阅读