技术积累

明日复明日,明日何其多,我生待明日,万事成蹉跎。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言:关于Remoting,我已经写了几篇文章了,就目前掌握的知识而言,差不多都是有关Remoting的基础知识。现在的我正准备向Remoting的高级用法进阶。那么就有必要将前期所做的学习和工作整理一下,何况以前写的文章因为自己一知半解的缘故,自有矛盾之处,所以也能借此机会对其进行修改。望各位同学多多指正。


一、Remoting基础


什么是Remoting,简而言之,我们可以将其看作是一种分布式处理方式。从微软的产品角度来看,可以说Remoting就是DCOM的一种升级,它改善了很多功能,并极好的融合到.Net平台下。Microsoft® .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这也正是我们使用Remoting的原因。为什么呢?在Windows操作系统中,是将应用程序分离为单独的进程。这个进程形成了应用程序代码和数据周围的一道边界。如果不采用进程间通信(RPC)机制,则在一个进程中执行的代码就不能访问另一进程。这是一种操作系统对应用程序的保护机制。然而在某些情况下,我们需要跨过应用程序域,与另外的应用程序域进行通信,即穿越边界。


在Remoting中是通过通道(channel)来实现两个应用程序域之间对象的通信的。如图所示:


首先,客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象。这就提供一种可能性,即以服务的方式来发布服务器对象。远程对象代码可以运行在服务器上(如服务器激活的对象和客户端激活的对象),然后客户端再通过Remoting连接服务器,获得该服务对象并通过序列化在客户端运行。


在Remoting中,对于要传递的对象,设计者除了需要了解通道的类型和端口号之外,无需再了解数据包的格式。但必须注意的是,客户端在获取服务器端对象时,并不是获得实际的服务端对象,而是获得它的引用。这既保证了客户端和服务器端有关对象的松散耦合,同时也优化了通信的性能。


1、Remoting的两种通道


Remoting的通道主要有两种:Tcp和Http。在.Net中,System.Runtime.Remoting.Channel中定义了IChannel接口。IChannel接口包括了TcpChannel通道类型和Http通道类型。它们分别对应Remoting通道的这两种类型。

TcpChannel类型放在名字空间System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基于Socket的传输工具,使用Tcp协议来跨越Remoting边界传输序列化的消息流。TcpChannel类型默认使用二进制格式序列化消息对象,因此它具有更高的传输性能。HttpChannel类型放在名字空间System.Runtime.Remoting.Channel.Http中。它提供了一种使用Http协议,使其能在Internet上穿越防火墙传输序列化消息流。默认情况下,HttpChannel类型使用Soap格式序列化消息对象,因此它具有更好的互操作性。通常在局域网内,我们更多地使用TcpChannel;如果要穿越防火墙,则使用HttpChannel。


2、远程对象的激活方式


在访问远程类型的一个对象实例之前,必须通过一个名为Activation的进程创建它并进行初始化。这种客户端通过通道来创建远程对象,称为对象的激活。在Remoting中,远程对象的激活分为两大类:服务器端激活和客户端激活。

(1) 服务器端激活,又叫做WellKnow方式,很多又翻译为知名对象。为什么称为知名对象激活模式呢?是因为服务器应用程序在激活对象实例之前会在一个众所周知的统一资源标识符(URI)上来发布这个类型。然后该服务器进程会为此类型配置一个WellKnown对象,并根据指定的端口或地址来发布对象。.Net Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种。


SingleTon模式:此为有状态模式。如果设置为SingleTon激活方式,则Remoting将为所有客户端建立同一个对象实例。当对象处于活动状态时,SingleTon实例会处理所有后来的客户端访问请求,而不管它们是同一个客户端,还是其他客户端。SingleTon实例将在方法调用中一直维持其状态。举例来说,如果一个远程对象有一个累加方法(i=0;++i),被多个客户端(例如两个)调用。如果设置为SingleTon方式,则第一个客户获得值为1,第二个客户获得值为2,因为他们获得的对象实例是相同的。如果熟悉Asp.Net的状态管理,我们可以认为它是一种Application状态。


SingleCall模式:SingleCall是一种无状态模式。一旦设置为SingleCall模式,则当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例,至于对象实例的销毁则是由GC自动管理的。同上一个例子而言,则访问远程对象的两个客户获得的都是1。我们仍然可以借鉴Asp.Net的状态管理,认为它是一种Session状态。


(2) 客户端激活。与WellKnown模式不同,Remoting在激活每个对象实例的时候,会给每个客户端激活的类型指派一个URI。客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。SingleCall模式和客户端激活模式是有区别的:首先,对象实例创建的时间不一样。客户端激活方式是客户一旦发出调用的请求,就实例化;而SingleCall则是要等到调用对象方法时再创建。其次,SingleCall模式激活的对象是无状态的,对象生命期的管理是由GC管理的,而客户端激活的对象则有状态,其生命周期可自定义。其三,两种激活模式在服务器端和客户端实现的方法不一样。尤其是在客户端,SingleCall模式是由GetObject()来激活,它调用对象默认的构造函数。而客户端激活模式,则通过CreateInstance()来激活,它可以传递参数,所以可以调用自定义的构造函数来创建实例。


二、远程对象的定义


前面讲到,客户端在获取服务器端对象时,并不是获得实际的服务端对象,而是获得它的引用。因此在Remoting中,对于远程对象有一些必须的定义规范要遵循。

由于Remoting传递的对象是以引用的方式,因此所传递的远程对象类必须继承MarshalByRefObject。MSDN对MarshalByRefObject的说明是:MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承MarshallByRefObject。


以下是一个远程对象类的定义:

public class ServerObject:MarshalByRefObject
{
        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }
}
这个类只实现了最简单的方法,就是设置一个人的基本信息,并返回一个Person类对象。注意这里返回的Person类。由于这里所传递的Person则是以传值的方式来完成的,而Remoting要求必须是引用的对象,所以必须将Person类序列化。


因此,在Remoting中的远程对象中,如果还要调用或传递某个对象,例如类,或者结构,则该类或结构则必须实现串行化Attribute[SerializableAttribute]。


[Serializable]
 public class Person
 {
        public Person()
        {
           
        }

        private string name;
        private string sex;
        private int age;

        public string Name
        {
            get    {return name;}
            set    {name = value;}
        }

        public string Sex
        {
            get {return sex;}
            set {sex = value;}
        }

        public int Age
        {
            get {return age;}
            set {age = value;}
        }
  }

将该远程对象以类库的方式编译成Dll。这个Dll将分别放在服务器端和客户端,以添加引用。


在Remoting中能够传递的远程对象可以是各种类型,包括复杂的DataSet对象,只要它能够被序列化。远程对象也可以包含事件,但服务器端对于事件的处理比较特殊,本文不涉及。


三、服务器端

根据第一部分所述,根据激活模式的不同,通道类型的不同服务器端的实现方式也有所不同。大体上说,服务器端应分为三步:


1、注册通道


要跨越应用程序域进行通信,必须实现通道。如前所述,Remoting提供了IChannel接口,分别包含TcpChannel和HttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外,实现的方式完全一致,因此下面我们就以TcpChannel为例。


注册TcpChannel,首先要在项目中添加引用“System.Runtime.Remoting”,然后using名字空间:System.Runtime.Remoting.Channel.Tcp。代码如下:

            TcpChannel channel = new TcpChannel(8080);
            ChannelServices.RegisterChannel(channel);

在实例化通道对象时,将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。


2、注册远程对象


注册了通道后,要能激活远程对象,必须在通道中注册该对象。根据激活模式的不同,注册对象的方法也不同。


(1) SingleTon模式


对于WellKnown对象,可以通过静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现:

RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleTon);

(2)SingleCall模式


注册对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。

RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

(3)客户端激活模式


对于客户端激活模式,使用的方法又有不同,但区别不大,看了代码就一目了然。

RemotingConfiguration.ApplicationName = "ServiceMessage";
RemotingConfiguration.RegisterActivatedServiceType(
                typeof(ServerRemoteObject.ServerObject));

为什么要在注册对象方法前设置ApplicationName属性呢?其实这个属性就是该对象的URI。对于WellKnown模式,URI是放在RegisterWellKnownServiceType()方法的参数中,当然也可以拿出来专门对ApplicationName属性赋值。而RegisterActivatedServiceType()方法的重载中,没有ApplicationName的参数,所以必须分开。

3、注销通道


如果要关闭Remoting的服务,则需要注销通道,也可以关闭对通道的监听。在Remoting中当我们注册通道的时候,就自动开启了通道的监听。而如果关闭了对通道的监听,则该通道就无法接受客户端的请求,但通道仍然存在,如果你想再一次注册该通道,会抛出异常。


           //获得当前已注册的通道;
            IChannel[] channels = ChannelServices.RegisteredChannels;

            //关闭指定名为MyTcp的通道;
            foreach (IChannel eachChannel in channels)
            {
                if (eachChannel.ChannelName == "MyTcp")
                {
                    TcpChannel tcpChannel = (TcpChannel)eachChannel;

                    //关闭监听;
                    tcpChannel.StopListening(null);

                    //注销通道;
                    ChannelServices.UnregisterChannel(tcpChannel);
                }
            }
代码中,RegisterdChannel属性获得的是当前已注册的通道。在Remoting中,是允许同时注册多个通道的,这一点会在后面说明。


四、客户端


客户端主要做两件事,一是注册通道。这一点从图一就可以看出,Remoting中服务器端和客户端都必须通过通道来传递消息,以获得远程对象。第二步则是获得该远程对象。

1、注册通道:

TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);


注意在客户端实例化通道时,是调用的默认构造函数,即没有传递端口号。事实上,这个端口号是缺一不可的,只不过它的指定被放在后面作为了Uri的一部分。

2、获得远程对象。

与服务器端相同,不同的激活模式决定了客户端的实现方式也将不同。不过这个区别仅仅是WellKnown激活模式和客户端激活模式之间的区别,而对于SingleTon和SingleCall模式,客户端的实现完全相同。


(1) WellKnown激活模式

要获得服务器端的知名远程对象,可通过Activator进程的GetObject()方法来获得:

ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(
              typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");

首先以WellKnown模式激活,客户端获得对象的方法是使用GetObject()。其中参数第一个是远程对象的类型。第二个参数就是服务器端的uri。如果是http通道,自然是用http://localhost:8080/ServiceMessage了。因为我是用本地机,所以这里是localhost,你可以用具体的服务器IP地址来代替它。端口必须和服务器端的端口一致。后面则是服务器定义的远程对象服务名,即ApplicationName属性的内容。

(2) 客户端激活模式


如前所述,WellKnown模式在客户端创建对象时,只能调用默认的构造函数,上面的代码就说明了这一点,因为GetObject()方法不能传递构造函数的参数。而客户端激活模式则可以通过自定义的构造函数来创建远程对象。


客户端激活模式有两种方法:


1) 调用RemotingConfiguration的静态方法RegisterActivatedClientType()。这个方法返回值为Void,它只是将远程对象注册在客户端而已。具体的实例化还需要调用对象类的构造函数。

 RemotingConfiguration.RegisterActivatedClientType(               
                typeof(ServerRemoteObject.ServerObject),
                "tcp://localhost:8080/ServiceMessage");
 ServerRemoteObject.ServerObject serverObj = new ServerRemoteObject.ServerObject();

2) 调用进程Activator的CreateInstance()方法。这个方法将创建方法参数指定类型的类对象。它与前面的GetObject()不同的是,它要在客户端调用构造函数,而GetObject()只是获得对象,而创建实例是在服务器端完成的。CreateInstance()方法有很多个重载,我着重说一下其中常用的两个。


a、 public static object CreateInstance(Type type, object[] args, object[] activationAttributes);

参数说明:
type:要创建的对象的类型。
args :与要调用构造函数的参数数量、顺序和类型匹配的参数数组。如果 args 为空数组或空引用(Visual Basic 中为 Nothing),则调用不带任何参数的构造函数(默认构造函数)。
activationAttributes :包含一个或多个可以参与激活的属性的数组。

这里的参数args是一个object[]数组类型。它可以传递要创建对象的构造函数中的参数。从这里其实可以得到一个结论:WellKnown激活模式所传递的远程对象类,只能使用默认的构造函数;而Activated模式则可以用户自定义构造函数。activationAttributes参数在这个方法中通常用来传递服务器的url。

假设我们的远程对象类ServerObject有个构造函数:

            ServerObject(string pName,string pSex,int pAge)
            {
                name = pName;
                sex = pSex;
                age = pAge;
            }

那么实现的代码是:

            object[] attrs = {new UrlAttribute("tcp://localhost:8080/ServiceMessage")};
            object[] objs = new object[3];
            objs[0] = "wayfarer";
            objs[1] = "male";
            objs[2] = 28;
            ServerRemoteObject.ServerObject = Activator.CreateInstance(
                typeof(ServerRemoteObject.ServerObject),objs,attrs);
可以看到,objs[]数组传递的就是构造函数的参数。


b、public static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttribute);


参数说明:
assemblyName :将在其中查找名为 typeName 的类型的程序集的名称。如果 assemblyName 为空引用(Visual Basic 中为 Nothing),则搜索正在执行的程序集。
typeName:首选类型的名称。
activationAttributes :包含一个或多个可以参与激活的属性的数组。

参数说明一目了然。注意这个方法返回值为ObjectHandle类型,因此代码与前不同:

            object[] attrs = {new UrlAttribute("tcp://localhost:8080/EchoMessage")};           
            ObjectHandle handle = Activator.CreateInstance("ServerRemoteObject",
                                   "ServerRemoteObject.ServerObject",attrs);
            ServerRemoteObject.ServerObject obj = (ServerRemoteObject.ServerObject)handle.Unwrap();

这个方法实际上是调用的默认构造函数。ObjectHandle.Unwrap()方法是返回被包装的对象。


说明:要使用UrlAttribute,还需要在命名空间中添加:using System.Runtime.Remoting.Activation;

五、Remoting基础的补充


通过上面的描述,基本上已经完成了一个最简单的Remoting程序。这是一个标准的创建Remoting程序的方法,但在实际开发过程中,我们遇到的情况也许千奇百怪,如果只掌握一种所谓的“标准”,就妄想可以“一招鲜、吃遍天”,是不可能的。


1、注册多个通道


在Remoting中,允许同时创建多个通道,即根据不同的端口创建不同的通道。但是,Remoting要求通道的名字必须不同,因为它要用来作为通道的唯一标识符。虽然IChannel有ChannelName属性,但这个属性是只读的。因此前面所述的创建通道的方法无法实现同时注册多个通道的要求。


这个时候,我们必须用到System.Collection中的IDictionary接口:


注册Tcp通道:

IDictionary tcpProp = new Hashtable();
tcpProp["name"] = "tcp9090";
tcpProp["port"] = 9090;
IChannel channel = new TcpChannel(tcpProp,
 new BinaryClientFormatterSinkProvider(),
 new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);

注册Http通道:

IDictionary httpProp = new Hashtable();
httpProp["name"] = "http8080";
httpProp["port"] = 8080;
IChannel channel = new HttpChannel(httpProp,
 new SoapClientFormatterSinkProvider(),
 new SoapServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);

在name属性中,定义不同的通道名称就可以了。


2、远程对象元数据相关性


由于服务器端和客户端都要用到远程对象,通常的方式是生成两份完全相同的对象Dll,分别添加引用。不过为了代码的安全性,且降低客户端对远程对象元数据的相关性,我们有必要对这种方式进行改动。即在服务器端实现远程对象,而在客户端则删除这些实现的元数据。


由于激活模式的不同,在客户端创建对象的方法也不同,所以要分离元数据的相关性,也应分为两种情况。


(1) WellKnown激活模式:


通过接口来实现。在服务器端,提供接口和具体类的实现,而在客户端仅提供接口:

    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

public class ServerObject:MarshalByRefObject,IServerObject
{ ......}

注意:两边生成该对象程序集的名字必须相同,严格地说,是命名空间的名字必须相同。
           
(2) 客户端激活模式:

如前所述,对于客户端激活模式,不管是使用静态方法,还是使用CreateInstance()方法,都必须在客户端调用构造函数实例化对象。所以,在客户端我们提供的远程对象,就不能只提供接口,而没有类的实现。实际上,要做到与远程对象元数据的分离,可以由两种方法供选择:


a、利用WellKnown激活模式模拟客户端激活模式:


方法是利用设计模式中的“抽象工厂”,下面的类图表描述了总体解决方案:



我们在服务器端的远程对象中加上抽象工厂的接口和实现类:


    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

    public interface IServerObjFactory
    {
        IServerObject CreateInstance();       
    }

    public class ServerObject:MarshalByRefObject,IServerObject
    {
        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }       
    }

    public class ServerObjFactory:MarshalByRefObject,IServerObjFactory
    {
        public IServerObject CreateInstance()
        {
            return new ServerObject();
        }
    }

然后再客户端的远程对象中只提供工厂接口和原来的对象接口:


    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

    public interface IServerObjFactory
    {
        IServerObject CreateInstance();       
    }

我们用WellKnown激活模式注册远程对象,在服务器端:

           //传递对象;
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObjFactory),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

注意这里注册的不是ServerObject类对象,而是ServerObjFactory类对象。


客户端:

ServerRemoteObject.IServerObjFactory serverFactory =               
                (ServerRemoteObject.IServerObjFactory) Activator.GetObject(
                typeof(ServerRemoteObject.IServerObjFactory),
                "tcp://localhost:8080/ServiceMessage");

ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance();


为什么说这是一种客户端激活模式的模拟呢?从激活的方法来看,我们是使用了SingleCall模式来激活对象,但此时激活的并非我们要传递的远程对象,而是工厂对象。如果客户端要创建远程对象,还应该通过工厂对象的CreateInstance()方法来获得。而这个方法正是在客户端调用的。因此它的实现方式就等同于客户端激活模式。

b、利用替代类来取代远程对象的元数据


实际上,我们可以用一个trick,来欺骗Remoting。这里所说的替代类就是这个trick了。既然是提供服务,Remoting传递的远程对象其实现的细节当然是放在服务器端。而要在客户端放对象的副本,不过是因为客户端必须调用构造函数,而采取的无奈之举。既然具体的实现是在服务器端,又为了能在客户端实例化,那么在客户端就实现这些好了。至于实现的细节,就不用管了。


如果远程对象有方法,服务器端则提供方法实现,而客户端就提供这个方法就OK了,至于里面的实现,你可以是抛出一个异常,或者return 一个null值;如果方法返回void,那么里面可以是空。关键是这个客户端类对象要有这个方法。这个方法的实现,其实和方法的声明差不多,所以我说是一个trick。方法如是,构造函数也如此。


还是用代码来说明这种“阴谋”,更直观:

服务器端:

    public class ServerObject:MarshalByRefObject
    {
        public ServerObject()
        {
           
        }

        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }       
    }

客户端:


    public class ServerObject:MarshalByRefObject
    {
        public ServerObj()
        {
            throw new System.NotImplementedException();
        }

        public Person GetPersonInfo(string name,string sex,int age)
        {
            throw new System.NotImplementedException();
        }       
    }

比较客户端和服务器端,客户端的方法GetPersonInfo(),没有具体的实现细节,只是抛出了一个异常。或者直接写上语句return null,照样OK。我们称客户端的这个类为远程对象的替代类。

3、利用配置文件实现


前面所述的方法,于服务器uri、端口、以及激活模式的设置是用代码来完成的。其实我们也可以用配置文件来设置。这样做有个好处,因为这个配置文件是Xml文档。如果需要改变端口或其他,我们就不需要修改程序,并重新编译,而是只需要改变这个配置文件即可。


(1) 服务器端的配置文件:

<configuration>
  <system.runtime.remoting>
    <application name="ServerRemoting">
      <service>
        <wellknown mode="Singleton" type="ServerRemoteObject.ServerObject"

objectUri="ServiceMessage"/>
      </service>
      <channels>
         <channel ref="tcp" port="8080"/>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

如果是客户端激活模式,则把wellknown改为activated,同时删除mode属性。


把该配置文件放到服务器程序的应用程序文件夹中,命名为ServerRemoting.config。那么前面的服务器端程序直接用这条语句即可:
RemotingConfiguration.Configure("ServerRemoting.config");


(2) 客户端配置文件


如果是客户端激活模式,修改和上面一样。调用也是使用RemotingConfiguration.Configure()方法来调用存储在客户端的配置文件。


配置文件还可以放在machine.config中。如果客户端程序是web应用程序,则可以放在web.config中。

4、启动/关闭指定远程对象


Remoting中没有提供类似UnregisterWellKnownServiceType()的方法,也即是说,一旦通过注册了远程对象,如果没有关闭通道的话,该对象就一直存在于通道中。只要客户端激活该对象,就会创建对象实例。如果Remoting传送的只有一个远程对象,这不存在问题,关闭通道就可以了。如果传送多个远程对象呢?要关闭指定的远程对象应该怎么做?关闭之后又需要启动又该如何?


我们注意到在Remoting中提供了Marshal()和Disconnect()方法,答案就在这里。Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象,这个对象是存储生成代理以与远程对象通讯所需的所有相关信息。这样就可以将该实例序列化以便在应用程序域之间以及通过网络进行传输,客户端就可以调用了。而Disconnect()方法则将具体的实例对象从通道中断开。


方法如下:


首先注册通道:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);

接着启动服务:

先在服务器端实例化远程对象。
ServerObject obj = new ServerObject();

然后,注册该对象。注意这里不用RemotingConfiguration.RegisterWellKnownServiceType(),而是使用RemotingServices.Marshal():

ObjRef objrefWellKnown = RemotingServices.Marshal(obj, "ServiceMessage");

如果要注销对象,则:
RemotingServices.Disconnect(obj);

要注意,这里Disconnect的类对象必须是前面实例化的对象。正因为此,我们可以根据需要创建指定的远程对象,而关闭时,则Disconnect之前实例化的对象。


至于客户端的调用,和前面WellKnown模式的方法相同,仍然是通过Activator.GetObject()来获得。但从实现代码来看,我们会注意到一个问题,由于服务器端是显式的实例化了远程对象,因此不管客户端有多少,是否相同,它们调用的都是同一个远程对象。因此我们将这个方法称为模拟的SingleTon模式。


客户端激活模式


我们也可以通过Marshal()和Disconnect()来模拟客户端激活模式。首先我们来回顾“远程对象元数据相关性”一节,在这一节中,我说到采用设计模式的“抽象工厂”来创建对象实例,以此用SingleCall模式来模拟客户端激活模式。在仔细想想前面的模拟的SingleTon模式。是不是答案就将呼之欲出呢?


在“模拟的SingleTon”模式中,我们是将具体的远程对象实例进行Marshal,以此让客户端获得该对象的引用信息。那么我们换一种思路,当我们用抽象工厂提供接口,工厂类实现创建远程对象的方法。然后我们在服务器端创建工厂类实例。再将这个工厂类实例进行Marshal。而客户端获取对象时,不是获取具体的远程对象,而是获取具体的工厂类对象。然后再调用CreateInstance()方法来创建具体的远程对象实例。此时,对于多个客户端而言,调用的是同一个工厂类对象;然而远程对象是在各个客户端自己创建的,因此对于远程对象而言,则是由客户端激活,创建的是不同对象了。


当我们要启动/关闭指定对象时,只需要用Disconnet()方法来注销工厂类对象就可以了。


六、小结


Microsoft.Net Remoting真可以说是博大精深。整个Remoting的内容不是我这一篇小文所能尽述的,更不是我这个Remoting的初学者所能掌握的。王国维在《人间词话》一书中写到:古今之成大事业大学问者,必经过三种境界。“昨夜西风凋碧树,独上高楼,望尽天涯路。”此第一境界也。“衣带渐宽终不悔,为伊消得人憔悴。”此第二境界也。“众里寻他千百度,蓦然回首,那人却在灯火阑珊处。”此第三境界也。如以此来形容我对Remoting的学习,还处于“独上高楼,望尽天涯路”的时候,真可以说还未曾登堂入室。


或许需得“衣带渐宽”,学得Remoting“终不悔”,方才可以“蓦然回首”吧。

posted on 2004-07-30 20:44 wayfarer 阅读(9640) 评论(88)  编辑 收藏 收藏至365Key 所属分类: .Net Remoting

评论

# re: Microsoft .Net Remoting[基础篇] 2004-07-30 23:52 寒枫天伤
文章真不错!

你的图画得真漂亮,是用什么画的?
  

# re: Microsoft .Net Remoting[基础篇] 2004-07-31 10:48 wayfarer
可不是我画的,在网上截的图,嘿嘿:)
  

# re: Microsoft .Net Remoting[基础篇] 2004-07-31 16:42 吕震宇
在微软的“企业模式”中好像看到过一些图和内容。我正在酝酿一篇《用Remoting技术传递Event》(题目还没有想好,可能还会改),有了这篇文章,看来可以省不少字了。
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-09 15:09 DYFILE
好文章。
再来两篇。。。。
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 15:50 游客
不错,我有个问题,说起来丢人。
看以前的代码,我只是用了TcpChannel  tcp = new TcpChannel();来声明一个tcp通道,并没有显示的注册,但是程序也能跑,这是为什么?系统会自动注册吗?
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 15:53 wayfarer
你是指不通过ChannelServices.RegisterChannel()方法来注册吗?
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 16:03 wayfarer
你是指不通过ChannelServices.RegisterChannel()方法来注册吗?这个问题比较有趣!我测试了一下,如果你在服务器端没有通过RegisterChannel()方法显式注册,在运行服务器端时并没有错误;但如果你在客户端去激活服务器端对象,则会抛出异常:“此远程处理代理没有信道接收,这意味着服务器没有正在侦听的已注册服务器信道,或者此应用程序没有用来与服务器对话的适当客户端信道。”说明该通道不能使用。

不过如果在客户端不注册通道,对Remoting没有影响。但我建议你最好在构造通道之后,对他进行注册。否则,当你注销通道时,也许会发生不可预料的错误。
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 16:52 csl
1、注册通道:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
注意在客户端实例化通道时,是调用的默认构造函数,即没有传递端口号。事实上,这个端口号是缺一不可的,只不过它的指定被放在后面作为了Uri的一部分

Some additional comments:
1. I think it is not necessary to register a client channel since remoting is built on the top of TCPIP.  A TCPIP client will randomly pick up a available port.  (Just like you don't need to specify which port for your IE to communicate with the web server which normally use well-known port 80)

2. But if you do specify a client port (e.g. new TcpChannel(3128)), the client will use that port.

3. You can even use tcpChannel(0)!  But it doesn't mean that the client can use port 0.  This is used to define a bi-directional communication (if your client needs to receive event from the remoting server).

4. Type "netstat -n 2" at dos prompt will let you to visualize what actually happen ....
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 17:02 wayfarer
同意csl,对于端口号而言,在服务器端构造通道时,通常应该指定具体的端口。如果端口号不确定,可以在构造函数中使用0。但它表示的并非端口号为0,而是指系统随机选择端口。但由于我们在客户端激活服务对象时,必须指定具体的uri。而这个uri有一个重要的组成部分就是端口号。如果采用随机端口号,客户端无法捕捉。

不过在客户端构造了端口之后,虽然可以不用再注册通道,但我还是建议应该注册。

@csl

我在我的英文博客中整理了对Remoting通道占用的讨论,不知道是否正确,你可以去看看:)

http://dotnetjunkies.com/WebLog/wayfarer/
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 17:57 csl
"我在我的英文博客中整理了对Remoting通道占用的讨论,不知道是否正确,你可以去看看"
Sure I will!  I can learn a lot from your great articles! :D


"但由于我们在客户端激活服务对象时,必须指定具体的uri。而这个uri有一个重要的组成部分就是端口号。如果采用随机端口号,客户端无法捕捉"
I mean we definitely need to specify a known port number at server.  However, we can let the client to pick a port number for itself.
Here I brief define what a socket is (it help to understand remoting):
When two parties (e.g. a client and a server) need to communicate using tcp protocol (.Net Remoting is an example).  They need to establish a tcp channel/link.  A tcp channel consists four critical elements: 
1. Source IP address (i.e. IP address of your Remoting client application who need to trigger a link)
2. Source Port number (i.e. the port a Remoting client application need to use itself, please note this is NOT the server port you defined using RegisterChannel at server-side!)
3. Target IP address (i.e. the IP address of your Remoting server hosting well-known objects)
4. Target Port number (i.e. the port you defined for Remoting server shown in URI

With all these element, they can form a tcp channel ( also known as sockets).

Here's an example:
Suppose 
Client IP = 192.168.0.5 (Remoting client application's machine)
Client Port = 3122 (Randomly pick up by client if you don't register a client channel)

Target IP =  192.168.0.1 (Remoting server machine hosting well-known objects)
Target Port = 8080  (This is the port shown in URI and defined using RegisterChannel at server!!!)       

Thus, a socket can be establish as follows:

192.168.0.5:3122 <-----TCP Channel---->192.168.0.1:8080

Here's the main point:
You can use any port number to substitute 3122, but you must specify a target port for server-side (i.e. 8080 for the above example)


  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 18:10 wayfarer
I am not familiar with socket. But I don't agree your opinion that the client-side may use any port number in Remoting.

I did a test. In my program, I used the port 8080 in the Server-side, and used the port 9090 in the Client-Side to get the remoting object, but it thrown a exception:"不能做任何连接,因为目标机器积极地拒绝它。".

Following is my code:

Server-Side:

channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);

ServerObject.ServiceFactory obj = new ServerObject.ServiceFactory();
ObjRef objRef = RemotingServices.Marshal((MarshalByRefObject)obj,"Service1");

Client-Side:

TcpChannel tcpChannel = new TcpChannel();
ChannelServices.RegisterChannel(tcpChannel);

ServerObject.IServiceFactory factory = (ServerObject.IServiceFactory)Activator.GetObject(typeof(ServerObject.IServiceFactory),"tcp://localhost:9080/Service1");

What happen?

  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 18:21 csl
From this line, I know you still have some misunderstanding :
ServerObject.IServiceFactory factory = (ServerObject.IServiceFactory)Activator.GetObject(typeof(ServerObject.IServiceFactory),"tcp://localhost:9080/Service1"); 


You OF COURSE need to type ServerObject.IServiceFactory factory = (ServerObject.IServiceFactory)Activator.GetObject(typeof(ServerObject.IServiceFactory),"tcp://localhost:8080/Service1"); 

What I mean is the port used for client ITSELF, NOT THE PORT IT SPECIFY TO COMMUNICATE WITH THE SERVER (8080)!

Please type "netstat -n 2" at dos prompt when your client is invoking server object.  You will probably see something like this

========================================
C:\WINDOWS>netstat -n

Active Connections

  Proto  Local Address          Foreign Address        State
  TCP    218.103.220.175:XXXX   207.68.172.239:8080      TIME_WAIT
========================================

I mean you can use any number for XXXX (if your client won't receive server event), not 8080

Sorry for my poor explanation (this is why I can write great article like yours..hehehe...)

  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 18:25 csl
this is why I can write great article like yours

Should be 

this is why I CAN'T write great article like yours

  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 18:42 wayfarer
I see. I misunderstand you mean.

It's not your fault, it's mine. Thanks. I learn more knowledge from your feedback again.
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 18:52 csl
Never mind!

Frankly, after reading this series of your articles regarding Remoting, I have reached a whole new level of understanding within this area indeed!

just reading your English blog .....
  

# re: Microsoft .Net Remoting[基础篇] 2004-08-12 18:58 wayfarer
:)

me too.
  

# re: Microsoft .Net Remoting[基础篇] 2004-09-06 13:47 buaaytt
这种方法能再讲详细么?比如具体初始化远程对象和调用代码该怎么写呢?我现在就需要带参数的构造函数
另外:采用服务端还是客户端激活?通常是怎么选择的呢?

  

# re: Microsoft .Net Remoting[基础篇] 2004-09-06 15:50 wayfarer
文章里面应该有说明了啊,根据你选择激活模式的不同,在服务器端注册远程对象和在客户端调用远程对象的方法也不同。

服务器端注册远程对象,请看第三部分服务器端“注册远程对象”;客户端调用远程对象,请看第四部分客户端“获得远程对象”。

需要带参数的构造函数,只能采用客户端激活,因为只有客户端激活,才能使用CreateInstance()方法,代码如下:
object[] attrs = {new UrlAttribute("tcp://localhost:8080/ServiceMessage")};
object[] objs = new object[3];
objs[0] = "wayfarer";
objs[1] = "male";
objs[2] = 28;
ServerRemoteObject.ServerObject = Activator.CreateInstance(
typeof(ServerRemoteObject.ServerObject),objs,attrs);

objs数组存储的内容就是构造函数参数传递的值。

激活模式的区别,文章也有描述了。怎么选择主要看你的需求。如果你所使用的远程对象,对于所有客户端而言都使用同一个对象,可以选择SingleTon模式。如果你需要每调用一次方法就激活一次,且激活后的对象不进行生命周期的管理,则选择SingleCall模式。至于客户端激活模式,则是在客户端激活对象时,该对象就产生了,你可以管理它的生命周期。
  

# re: Microsoft .Net Remoting[基础篇] 2004-09-06 16:09 longsan
provider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

因为.net1.1的安全性
  

# re: Microsoft .Net Remoting[基础篇] 2004-09-07 12:15 wayfarer
@longsan

你是指什么呢?

  

# re: Microsoft .Net Remoting[基础篇] 2004-09-07 21:37 wlz
能用
serverobject 访问 server端的其他对象吗?
比如我用form启动,加载serverobject,
当客户端调用时,调用serverobject,但是消息不是回显,而是显示在server的form上?
  

# re: Microsoft .Net Remoting[基础篇] 2004-09-08 12:33 wayfarer
作为ServerObject,在其方法内部所调用的对象都是服务器端对象,只有传递的参数和返回值会完成和客户端的通讯。前提是参数值和返回值是可序列化的。

因此在客户端调用服务对象时,如果用MessageBox显示信息的方法是放在服务对象中,则该消息会显示在服务器端。
  

# re: Microsoft .Net Remoting[基础篇] 2004-09-23 15:09 landyz
"在Remoting中能够传递的远程对象可以是各种类型,包括复杂的DataSet对象,只要它能够被序列化"
我想起在一个程序中想要通过Remoting传送Bitmap,却抛出异常,为何这样?
I am working on a project using .NET remoting. I can get the server object back at client... i can call function also.. But i am having trouble with a function returning an Image Class object.. I can receive it on client in an Image class object but i get following exceptions:

An unhandled exception of type

'System.Runtime.Remoting.RemotingException' occurred in
system.windows.forms.dll
Additional information: Remoting cannot find field nativeImage on type
System.Drawing.Image

when i use a server returned Image object in Graphics.DrawImage function..

why is it so ??

  

# re: Microsoft .Net Remoting[基础篇] 2004-09-27 15:49 landyz
把BITMAP序列化成流,问题解决
  

# re: Microsoft .Net Remoting[基础篇] 2004-10-09 11:33 skywood
文中说道:
在“模拟的SingleTon”模式中,我们是将具体的远程对象实例进行Marshal,以此让客户端获得该对象的引用信息。那么我们换一种思路,当我们用抽象工厂提供接口,工厂类实现创建远程对象的方法。然后我们在服务器端创建工厂类实例。再将这个工厂类实例进行Marshal。而客户端获取对象时,不是获取具体的远程对象,而是获取具体的工厂类对象。然后再调用CreateInstance()方法来创建具体的远程对象实例。此时,对于多个客户端而言,调用的是同一个工厂类对象;然而远程对象是在各个客户端自己创建的,因此对于远程对象而言,则是由客户端激活,创建的是不同对象了。

=============
可是这样以来,客户端又如何对各自远程对象的生命周期进行管理呢?这样通过工厂类生成的远程对象是否也能参与它的生命周期管理呢?能详细说明一下吗?
  

# re: Microsoft .Net Remoting[基础篇] 2004-10-09 17:47 wayfarer
首先工厂对象的生命周期可以通过重写InitializeLifetimeService()法方法,设定其生命周期的值。至于通过工厂类创建的远程对象,由于其同样派生自MarshByRefObject,因此仍然可以重写InitializeLifetimeService()方法来管理。因为虽然这个对象是在客户端激活创建的,但该对象仍然创建在服务器端,因此管理的方式是一致的。




  

# re: Microsoft .Net Remoting[基础篇] 2004-10-09 19:46 Bigbigpoo
请问能不能使用客户端到Web应用程序的访问呢?
我在Web.config里配置了文中写的XML配置内容。但是,使用TCP通道的时候,在客户端就是找不到服务器端。
有没有使用TCP通道,并且是WellKnown激活模式的客户端和Web.config的例子呢? 
谢谢!
  

# re: Microsoft .Net Remoting[基础篇] 2004-10-09 20:49 wayfarer
@Bigbigpoo
我对你的问题有些疑惑不解。Remoting主要应用在分布式处理,要进行分布式开发,必须具备三部分内容:远程对象、服务器端、客户端。如果是要访问Web应用程序。如果你是用ASP.NET开发的话,应该属于B/S方式。通过Http协议及浏览器对服务端的应用程序进行访问,只要你的IIS设置正确。

如果一定要使用Remoting,可以将Remoting放到IIS中以Web Service的方式进行。

Remoting的配置文件是一个单独的配置文件,并通过Configure()方来调入给配置文件。从理论上说,可以将配置文件放到Web.Config中,在实际中应该也是可行的。不过我还未曾将Remoting集合到ASP.NET的经验。

所以,可能需要查阅一些资料,才能知道答案。很抱歉现在还无法回答你的问题。



  

# re: Microsoft .Net Remoting[基础篇] 2004-10-10 19:50 wayfarer
@Bigbigpoo
首先应该承认我的错误。因为没有在ASP.NET下部署过Remoting,所以在上面给你的答复中出现了错误。

事实上,使用ASP.NET使用Remoting是很常见的一种用法,此时ASP.NET应用程序是作为Remoting的客户端,它通过Remoting去调用服务器端的服务。其实,这完全类似调用WebService,只是实现的方式不同而已。

我在文章中写到的配置文件应是正确的,不过在Web.config中应该是设置客户端。你只需要把<system.runtime.remoting>一节写到<configuration>节点中就可以了。请注意大小写。

另外,如果你部署到IIS中,请注意消息名应加上后缀.rem或.soap。


  

# re: Microsoft .Net Remoting[基础篇] 2004-10-11 18:04 Bigbigpoo
能够使用TCP的Channel并且是使用WellKnown激活模式吗?
  

# re: Microsoft .Net Remoting[基础篇] 2004-10-28 16:47 sunhappy1
请问能不能使用客户端到Web应用程序的访问呢?
我在Web.config里配置了文中写的XML配置内容。但是,使用TCP通道的时候,在客户端就是找不到服务器端。
有没有使用TCP通道,并且是WellKnown激活模式的客户端和Web.config的例子呢? 
谢谢!


如是是Web应用程序,部署到IIS上,一定要走HTTP通道!
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-16 17:54 wangliangzhong
好文章,我们现在正在做一个pdm项目,准备采用微软的智能客户端技术实现,可能需要用到remoting,一个问题是大系统采用remoting,远程对象会不会过多.
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-17 11:56 wayfarer
有这个问题。所以需要合理管理远程对象的生命周期。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-18 14:37 buaaytt
很久没来了,我上次问的如何选择激活模式的意思是:各种激活模式在性能等方面有什么差异?各自有什么优缺点?
知道了这个,我才好选择是使用哪种激活模式。而不是说简单地决定使用SingleCall还是SingleTon

另外:
能够讲一下远程对象生存期的管理吗?
对于不同的远程对象和激活模式,生存期管理是怎么一回事呢?

比如:从你的文章里只是简单说了SingleCall是由GC管理,客户端激活的则可以自定义生存期,那么SingleTon模式的呢?
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-18 17:30 wayfarer
关于生命周期管理,请看我的这篇文章:《Microsoft .Net Remoting系列专题之一:Marshal、Disconnect与生命周期以及跟踪服务

关于三种激活方式的区别,请看我的这篇文章:《Microsoft .Net Remoting系列专题之二:从实例谈Remoting的激活模式及相关技术》,当然这篇文章我还没写完。但应该可以解决你的问题了。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-24 15:10 linaren
我是带着问题搜了一下remoting就看到你的文章,内容很系统也很详细的,要是我能早些时候看到的话,有一些共性问题也就不花费那么长时间了,
现在我有个新的问题就是怎样在一个应用程序里注册多个远程对象?我查看了我有的资料都没能解决,
请教你一下,望不吝赐教,谢谢!
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-24 18:26 wayfarer
谢谢linaren。在我的博客里,目前的Remoting知识还是一些基本的介绍,在博客园里,还有很多研究Remoting的,如Rickie,吕震宇等。你可以就在博客园里搜索。至于你的问题,怎样在一个应用程序里注册多个远程对象?

就我目前所知,注册多个远程对象完全没有问题的啊,实现的方法和注册一个远程对象的方法没有区别,只需要你所设置的url的message名设置成唯一的就可以了。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-25 12:56 linaren
:)谢谢,但我这样注册总是不行,麻烦看一下以下代码是错在那里

(server)
//注册访问远程对象的通道
IDictionary tcpChannel_User = new Hashtable();
tcpChannel_User["name"] = "tcpChannel_User";
tcpChannel_User["port"] = port;
TcpChannel channel_user = new TcpChannel( tcpChannel_User,
new BinaryClientFormatterSinkProvider(),new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel( channel_user ); //注册用户对象为知名对象
System.Runtime.Remoting.WellKnownServiceTypeEntry ste1 =
new System.Runtime.Remoting.WellKnownServiceTypeEntry( typeof( object1 ) ,
"ServerRemotingUri1" ,
System.Runtime.Remoting.WellKnownObjectMode.SingleCall );

RemotingConfiguration.RegisterWellKnownServiceType( ste1 );

//注册用户对象为知名对象
System.Runtime.Remoting.WellKnownServiceTypeEntry ste2 =
new System.Runtime.Remoting.WellKnownServiceTypeEntry( typeof( object2 ) ,
"ServerRemotingUri2" ,
System.Runtime.Remoting.WellKnownObjectMode.SingleCall );

RemotingConfiguration.RegisterWellKnownServiceType( ste2 );

在客户端调用object1时一切OK,但调用object2时就会报
========================
试图创建object2类型的已知对象,已知对象必须从shalByRefObject
类派生...
========================

实际上object2就是继承MarshalByRefObject的
这是什么原因? object1与object2都是interface

:)再次请教了


  

# re: Microsoft .Net Remoting[基础篇] 2004-11-25 14:28 channelV
关于未注册通道程序也会运行的解释:【摘自.net文档】注意 可能不需要在客户端上注册信道。如果客户端未注册信道,远程处理系统将使用在 Machine.config 文件中指定的默认信道之一自动选择或创建一个信道以发出传出的请求。客户端上的这一自动信道选择并不注册信道来侦听服务器中的任何回调函数,并且不注册任何自定义信道实现(除非该自定义信道已添加到 machine.config 文件中)。这些情况下,必须在客户端应用程序域中注册要使用的信道类型。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-25 21:01 wayfarer
@linaren:
从代码上来看,服务器端没有问题。当然你后来的说法让我迷惑。你说object1和object2都是interface,而如果是interface则不能继承MarshalByRefObject。不过你说object1调用正常,这排除了我的疑惑。

我的理解是object1和object2应该是两个类,他们在继承MarshalByRefObject,同时又分别实现接口。而在 客户端调用的时候,则生成两者的接口。因为在服务器端注册远程对象时,typeof获取的应该是类的类型,而非interface.

你能把object1和object2两个对象及客户端的代码贴出来。可能是这些代码有误。我想,看到较为完整的代码后,我可以给你一个好的解答。

有一点我可以肯定,在一个通道中绝对可以注册多个对象,因为我已经在项目中实施过了,而且这个Remoting远程对象管理器运行得很好。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-25 21:03 wayfarer
@channelV:
你的补充很正确。在客户端完全可以不用注册通道。如果确实要注册通道,也不用指定端口号。

至于通道类型,在客户端调用远程对象时,会自动判断通道的类型。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-25 21:09 wayfarer
@linaren:
你在服务器端注册对象的方法实在太复杂了,其实可以更简单:
RemotingConfiguration.RegisterWellKnownServiceType(typeof(object1),"ServerRemotingUri1" ,
System.Runtime.Remoting.WellKnownObjectMode.SingleCall );

当然方法没问题,我比较喜欢简单。

如果你觉得注册多个对象有问题,你也可以用这个方法来注册:
object1 obj = new object1();
ObjRef objRef = RemotingServices.Marshal(obj,"ServerRemotingUri1");

在客户端用Activator.GetObject()方法。

  

# re: Microsoft .Net Remoting[基础篇] 2004-11-26 17:40 linaren
谢谢你的解答
:)不好意思,我说错了,是这样的
object1与object2都是接口,实现object1与object2的都是继承于
MarshalByRefObject的
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-26 17:51 linaren
这以具体的两个接口与实现
/// <summary>
/// User interface
/// </summary>
public interface UserPerform
{
DataTable GetUserList();
DataTable GetVipUserList();
string RegisterVipUser( User user );
string UnRegisterVipUser( User user );
string AlterPassWord( User user );
}//class interface

/// <summary>
/// Goods interface
/// </summary>
public interface GoodsPerform
{
DataTable GetAllGoods();
string AddGoods( Goods goods );
string DelGoods( Goods goods );
string ModGoods( Goods goods );
}


  

# re: Microsoft .Net Remoting[基础篇] 2004-11-26 17:52 linaren
public class GoodsPort : System.MarshalByRefObject,GoodsPerform
{
public GoodsPort()
{}
public DataTable GetAllGoods()
{
try
{
//CommonLibary.SerializePort.SerializeObject(
string sqlStr = "select * from "
+ DBMapping.GoodsTable.BaseInfoTable.TableName ;

DataTable dt = DataPort.GetDataPort().DBE.ExecQuery( sqlStr ).Tables[0] ;
return dt;
}
catch(Exception ex)
{
string str = ex.Message;
return null;
}
}
public string AddGoods( Goods goods ){ return "";}
public string DelGoods( Goods goods ){ return "";}
public string ModGoods( Goods goods ){ return "";}
}//class Goods

}

  

# re: Microsoft .Net Remoting[基础篇] 2004-11-26 18:04 linaren
GoodsPort这个调用就会出现上术的问题的

现在我又遇到一个问题 :)
是这样的的在一个方法的参数里有个一个自定义class类型
这个类里面有几个string字段,还有个Hashtable字段
如下
interface a{
string Done( MYClass myclass );
}

public class ima
{
public string Done(MYClass myclass );

{
.....
return "";
}
}
========================
客户端调用如下:
a A = (a)System.Activator.GetObject( typeof(a),
RemoteObjectUrl );
A.Done( myclass);

问题是:在执行A.Done( myclass )时竟会出现异常---索引超出数组界限!
这个是不是跟传参编组有关?我真的很迷惑

(附加说明:我把几个接口都在一个类中实现的,不知与这有关系没)
:)又要麻烦你解答了
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-26 18:05 linaren
这个类也加了序列化属性
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-26 19:00 wayfarer
我仔细看了你的代码,我想问清楚几点:
1、GoodsPort类,你在服务器端激活的时候,typeof()取的是GoodsPort,还是接口GoodsPerform?正确的做法是用类类型。
2、如果是调用的类类型,那么typeof()里面对于类类型是否描述正确?我不知道你这些类,接口和服务端程序是否都在一个命名空间下。你必须保证typeof()里的类类型是正确的,或者是完整的。
3、如果还是不对,那么我就需要写段代码来测试一下了。因为目前我正在出差,使用的机器是公司测试部门的,他们的机器上没有安装Visual Studio 2003,所以我需要在下周回去后,写段测试代码给你看。

你所说的后一个问题,即出现“索引超出数组界限”的异常,我还没有遇见过。首先你保证MYClass类是否添加了[Serializabled]? 如果已经添加了,那么是否是因为Hashtable的原因?我没看到你这个MyClass类的代码,不好下肯定的判断。

另外,我在实际运用中,并没有在传值类中用过Hashtable,我不知道Hashtable是否支持序列化。所以,我需要实际测试一下。

希望下周我回去之后,能给你一个满意的答案。这期间,你也请仔细检查一下代码,或者请教一下其他高手。

btw:我不明白你这句话:把几个接口都在一个类中实现的。

如果你是把几个接口都放在一个cs文件中,那时没有问题的。VS的元数据并不以文件为界限。你可以在一个cs文件中放n个接口和类。

如果你说的是一个类同时实现了几个接口,那也没有问题。Remoting支持这种做法。只要你保证在服务端和客户端都部署了这些接口,就OK。

  

# re: Microsoft .Net Remoting[基础篇] 2004-11-29 09:36 linaren
thank you very much!
前两天休息了,今天上班才看到的

===1、GoodsPort类,你在服务器端激活的时候,typeof()取的是GoodsPort,还是接口GoodsPerform?正确的做法是用类类型。

:: 我在typeof()取的是GoodsPerform,因为要是取GoodsPort的话,就达不到我要的组件分布效果,因为在GoodsPort里用到的其它有关的业务类还有数据操作等(这些我理解是可以完全只放在服务端的),要是用GoodsPerform的话,这些组件也都要放到Client端了,那样的话,整个架构可能是没多大意义了,
但不知你是怎样解决这个问题的。


===“索引超出数组界限”=====
这个问题我自己也正查看是什么原因的

=====btw:我不明白你这句话:把几个接口都在一个类中实现的。==
:) 可能是我的表达能力太差了,我的意思就是
是一个类同时实现了几个接口
因为我还没解决注册多个类型的问题,就只好这样能进展下去的
让你见笑了:)

~@~很感谢你能百忙之中来解答我的疑问的
(我自我介绍一下我的情况吧,由于公司前几个项目都是在最原始的C/S架构下做的,当然这是有原因的比如用户要求的时间等,现在考虑要进行改进的,因为这在实际应用中发现原来的模式存在有很多的不便之处,而我现在就是负责探索采用.Remoting体系结构的可行性与实际的技术点探索,我以前对这的确是知之甚少-_-,现在发现这其中的东西多着呢!)


  

# re: Microsoft .Net Remoting[基础篇] 2004-11-29 10:09 linaren
:)
===“索引超出数组界限”=====
这个问题我自己也正查看是什么原因的
====================
这个是我自己搞错了,引用了在Hashtable中不存在的key
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-29 13:06 wayfarer
@linaren:
如果我没有理解错的话,我想我找到你的错误原因了。
GoodsPerform是接口,GoodsPort是类类型,同时也是你的远程对象。GoodsPort处理了很多业务,这些业务当然是放在服务器端的。如果GoodsPerform只是GoodsPort类对象中一部分业务的接口的话,那么该类中的其它业务还需要定义接口。

关键的一点是:在服务器端,你只能激活类对象,即typeof()只能取GoodsPort。道理很简单,首先接口并不能派生MarshalByRefObject,所以才会出现你前面说的异常。其次,这里所谓的激活,就是要创建该对象的实例,接口当然是不能创建实例的了。

你想把业务分类开,以实现分布式处理。这很简单啊,由于你的远程对象类GoodsPort实现了GoodsPerform接口。所以,你只需要在客户端放上接口定义就可以了。此时,在客户端获得远程对象的时候,这里的typeof()才是取接口类型:
GoodsPerform perform = (GoodsPerform)Activator.GetObject(typeof(GoodsPerform));

btw:我提醒一下,在为接口类型命名时,最好在该名字前加上“I”,即将GoodsPerform 接口命名为IGoodsPerform 。这样便于区别接口类型和类类型。因为,我发现你在写的时候,你常常把这两种对象弄混淆。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-30 08:51 linaren
to wayfarer
first i am obliged to you for your help!
但是我对你的这段解答
##############################
关键的一点是:在服务器端,你只能激活类对象,即typeof()只能取GoodsPort。道理很简单,首先接口并不能派生MarshalByRefObject,所以才会出现你前面说的异常。其次,这里所谓的激活,就是要创建该对象的实例,接口当然是不能创建实例的了。
##############################
还是有些迷惑的地方
1. 你说的typeof()是指注册时的还是在Client调用时的?
我在注册时typeof()里取的就是具体的实现类,不是接口,client端调用与你写的
==========================
GoodsPerform perform = (GoodsPerform)Activator.GetObject(typeof(GoodsPerform));
===============================
是一致的。

2.我注册了两个类型(UserPort与GoodsPort,当然都是继承于MarshalByRefObject的)
UserPort实现了IUser接口,GoodsPort实现了IGoodsPerform接口
,为什么第一个注册的可以正常调用,第二个就出现我开始说的问题(试图创建object2类型的已知对象,已知对象必须从shalByRefObject )?
**我的注册多个对象的代码已经在上面贴出来了

3. :) 希望你能给写个注册多个对象的示例代码,我学习一下,看是那里的问题。
  

# re: Microsoft .Net Remoting[基础篇] 2004-11-30 10:52 wayfarer
哦,原来是这样。那么按道理应该是没有问题的。
public interface IUserPerform
{
void Foo1();
}
public interface IGoodsPerform
{
void Foo2();
}
public class UserPort:MarshalByRefObject,IUserPerform
{
public void Foo1(){//代码略;}
}
public class GoodsPort:MarshalByRefObject,IGoodsPerform
{
public void Foo2(){//代码略;}
}
以上是远程对象;
TcpChannel channel = new TcpChannel(8000);
ChannerServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(UserPort),
"UserMessage",WellKnownObjectMode.SingleCall);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(GoodsPort),
"GoodsMessage",WellKnownObjectMode.SingleCall);
以上是服务端;
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
IUserPerform iUser = (IUserPerform)Activator.GetObject(typeof(IUserPerform));
iUser.Foo1();
IGoodsPerform iGoods = (IGoodsPerform)Activator.GetObject(typeof(IGoodsPerform));
iGoods.Foo2();
以上是客户端;
我只是凭记忆写的以上代码。因为在出差途中,机器上没有VS。如果有误的话,我想等我回去后,给你一个测试程序,你再看看。我还是肯定一点,注册多个对象是绝对没有问题的:)

  

# re: Microsoft .Net Remoting[基础篇] 2004-11-30 15:04 linaren
thanks agin !
After studying your code, i find out the matter!

问题出在了我在Client调用时引用的远程对象的uri错误了

IGoodsPerform iGoods = (IGoodsPerform)Activator.GetObject(typeof(IGoodsPerform), objectUri );

其中的objectUri错误了。

很是感谢你的!祝你旅途愉快,工作顺利!

忍不住又冒出个问题了 :)
我在这个问题解决之前,了解到可以注册多个通道,需要创建新的程序域。
但我不知这在什么情况下合适的,或者说这样有什么意义或用途,
:) 希望有空再解答一下吧!



  

# re: Microsoft .Net Remoting[基础篇] 2004-11-30 15:39 wayfarer
呵呵,解决了就好。遗憾的是我在给你回复的时候,客户端调用Activator.GetObject()方法也忘了写URI,呵呵,真是惭愧。

关于多通道的问题,我一直也还未完全解决。在本文中,已经描述了注册通道的方法,但是注册多通道后,怎样将对象注册到指定的通道中,一直还是个问题。所以我也没有一个好的解决方案。希望随着我的学习深入后,能解决你的问题。

  

# re: Microsoft .Net Remoting[基础篇] 2004-12-03 10:04 linaren
谢谢你
我以后有问题可能还要麻烦你的 :)
这两天我正搭建试验系统原型
知识可能是具有很强的繁衍性吧,由一个头开始我发现引发了很多东西的呵呵
  

# re: Microsoft .Net Remoting[基础篇] 2004-12-06 20:04 roky
不错,总结的很好
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2004-12-22 18:43 cenci
写的不错,多谢
请问SingleTon 和SingleCall是一次实例化一个对象还是可以实例化对象。(象EJB中SessionBean?)
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2004-12-22 21:31 wayfarer
SingleCall是在客户端每调用一次方法就实例化对象,之后马上销毁。而SingleTon则自始自终只实例化一个对象。

  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-01-17 14:35 aoyu
写了个remoting的project,采用客户端激活,
当我用soapsuds.exe为远程类生成替代类时,却发现生成的替代类中不包括远程类中带访问器的public属性,却将远程类中的private属性公开了。比如远程类Person里分别定义了
private string _Name;
public string Name
{
get
{
return _Name;
}

set
{
_Name = value;
}
但是在生成的代理类中就剩下了:
public string _Name.

很是郁闷,不知到原因出在哪里。
  

# Microsoft .Net Remoting系列专题之二:Marshal、Disconnect与生命周期以及跟踪服务(转载)[TrackBack] 2005-01-28 16:09 pqzemily
Ping Back来自:blog.csdn.net
pqzemily引用了该文章,地址:http://blog.csdn.net/pqzemily/archive/2005/01/28/272090.aspx
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-18 21:52 heping
写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。

由于我的项目内容比较多,这每个小模块,都增加了这么两层的工作,汇总起来,工作量就直线上升。

请教,我现在的结构如下,正确么?
项目1:客户端:界面层,访问客户端的RemoteObject项目;
项目2:客户端的RemoteObject项目:把应用层的每个类的方法用空的实现,写一次;考虑到应用层的类的数目庞大,若干个小模块,放在一个对象中。但这样,方法名,要作修改;
项目3:服务端的RemoteObject项目:客户端的RemoteObject项目,每个方法,调用应用层的类的方法;应用层、数据访问层;
项目4:公共对象:存放客户端和服务端各层都共享的数据对象;
项目5:服务端:调用服务端的RemoteObject项目。
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-18 21:53 heping
写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。

由于我的项目内容比较多,这每个小模块,都增加了这么两层的工作,汇总起来,工作量就直线上升。

请教,我现在的结构如下,正确么?
项目1:客户端:界面层,访问客户端的RemoteObject项目;
项目2:客户端的RemoteObject项目:把应用层的每个类的方法用空的实现,写一次;考虑到应用层的类的数目庞大,若干个小模块,放在一个对象中。但这样,方法名,要作修改;
项目3:服务端的RemoteObject项目:客户端的RemoteObject项目,每个方法,调用应用层的类的方法;应用层、数据访问层;
项目4:公共对象:存放客户端和服务端各层都共享的数据对象;
项目5:服务端:调用服务端的RemoteObject项目。
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-18 21:53 heping
写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。

由于我的项目内容比较多,这每个小模块,都增加了这么两层的工作,汇总起来,工作量就直线上升。

请教,我现在的结构如下,正确么?
项目1:客户端:界面层,访问客户端的RemoteObject项目;
项目2:客户端的RemoteObject项目:把应用层的每个类的方法用空的实现,写一次;考虑到应用层的类的数目庞大,若干个小模块,放在一个对象中。但这样,方法名,要作修改;
项目3:服务端的RemoteObject项目:客户端的RemoteObject项目,每个方法,调用应用层的类的方法;应用层、数据访问层;
项目4:公共对象:存放客户端和服务端各层都共享的数据对象;
项目5:服务端:调用服务端的RemoteObject项目。
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-19 21:24 wayfarer
@heping:
如果模块较多,最好不要采用替代类的方法。这样会让添加很多工作量。建议使用工厂方法,以WellKnown方式模拟客户端激活。

另外对于分布式开发,一定要注意方法。将接口单独放在一个程序集中,便于部署。
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-21 10:24 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-21 10:35 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-21 10:35 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-02-21 10:35 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”?
  

# Microsoft .Net Remoting系列专题 [TrackBack] 2005-02-25 08:16 LoveCherry
Ping Back来自:blog.csdn.net
LoveCherry引用了该文章,地址:http://blog.csdn.net/lovecherry/archive/2005/02/25/301142.aspx
  

# Microsoft .Net Remoting系列专题 [TrackBack] 2005-02-25 08:16 LoveCherry
Ping Back来自:blog.csdn.net
LoveCherry引用了该文章,地址:http://blog.csdn.net/lovecherry/archive/2005/02/25/301141.aspx
  

# .Net Remoting[TrackBack] 2005-03-05 17:11 philipsslg
Ping Back来自:blog.csdn.net
philipsslg引用了该文章,地址:http://blog.csdn.net/philipsslg/archive/2005/03/05/312103.aspx
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-03-14 13:39 city
我遇到一个问题就是:
客户端和服务器都是用配置文件配置的,server端直接调用RemotingConfiugre.config(配置文件)。然后一切正常,可是当客户端调用传入SqlParameter数组参数的时候异常:“此远程处理代理没有信道接收,这意味着服务器没有正在侦听的已注册服务器信道,或者此应用程序没有用来与服务器对话的适当客户端信道”。
而我改为string数组是没有问题的。可否指点一下?
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-03-29 13:58 Phoenix
我也在学习Remoting.看到作者这么热心的解答问题,真是让人感动.
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-03-31 11:12 skywood
其实采用替代类的方法也并不会增加多少工作量的,我没有怎么用过SoapSuds.exe这个工具,而且看他的命令行参数,感觉一不直观二也确实挺麻烦的。我在现在做的一个项目中就是自己来写这个生成器,虽然代码写的很烂,但也就百来行代码,用起来还可以,反正没出什么错,选中程序集就可以自动生成其中所有的替代类了。当然适用的情形可能都很简单,我的意思是实现这个生成器或者扩充功能并不难和麻烦,所以使用替代类这个方法我不觉得会有什么问题,我十分支持。对于楼主所说的"算不上是真正的分布式应用",不知道是什么意思,我并不这么认为。
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-04-20 22:46 form
在remoting中如何传递一个form呢?
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-05-04 17:24 阿扁
为什么要在客户端也要一个dll啊,感觉绕了一个圈.

  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-05-21 09:56 pdy
蝈蝈俊的文章我也看了,解决了不少问题。可是还有问题!
你有没有尝试过在通道中传输SqlParameter??
我找了整个网络也没找到解决方法
现在报的错是:
权限被拒绝: 无法远程调用非公共或静态方法
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-05-21 11:40 p
为什么不能调用remoting里的这个函数?

public SqlCommand BuildQueryCommand(string storedProcName, SqlParameter[] parameters)
{
try
{
if(myCn.State==ConnectionState.Closed)
myCn.Open();
SqlCommand command = new SqlCommand( storedProcName, myCn );
command.CommandType = CommandType.StoredProcedure;

foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add( parameter );
}
return command;
}
catch(Exception ex)
{
throw new Exception(ex.Message+"数据库操作失败~(由BuildQueryCommand引发!)\n");

}
finally
{
myCn.Close();
}
}


其他的都没问题
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-05-25 16:58 留心
//关闭监听;
tcpChannel.StopListening(null);

//注销通道;
ChannelServices.UnregisterChannel(tcpChannel)

我在服务器端"stop"按扭中写上以上代码后,但发现并不能关闭服务,客户端依然可与服务器连接,不知为什么:(

你的文章写的真不错

  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-07-22 15:36 花雨
报错:the underlying connection was closed:unable to connect to the remote server
各位好心人帮我看看!谢了

类代码://客户端和服务器端用来通讯的“共享命令集”
using System;
using System.Runtime;
using System.Data.SqlClient;

namespace DotNetRemoteTest
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
public class ResumeLoader:System.MarshalByRefObject
{
private SqlConnection dbConnection;
public ResumeLoader()
{
//
// TODO: 在此处添加构造函数逻辑
//
this.dbConnection = new System.Data.SqlClient.SqlConnection();
this.dbConnection.ConnectionString ="data source=YUANTT;initial catalog=gwcp_db;integrated security=SSPI;persist security info=False;workstation id=YUANTT;packet size=4096";
     System.Console.WriteLine("New Referance Added!");
}
public Resume GetResumeByUserID(decimal userid1)
{
Resume resume = new Resume(0);
try
{
dbConnection.Open();
SqlCommand cmd = new SqlCommand("SELECT resumeid,userid,title,body FROM Resume WHERE Resume.userid="+userid1+"",dbConnection);
SqlDataReader aReader = cmd.ExecuteReader();
if(aReader.Read())
{
resume.resumeid=aReader.GetDecimal(0);
resume.userid=aReader.GetDecimal(1);
resume.title=aReader.GetString(2);
resume.body=aReader.GetString(3);
}
aReader.Close();
dbConnection.Close();
}
catch(Exception x) { resume.title="Error:"+x; }
return resume;
}
}

[Serializable]
  public class Resume
  {
private decimal resumeid1, userid1;
private string body1,title1;
public Resume(decimal resumeid1)
{
this.resumeid=resumeid1;
this.userid=1;
this.body="This is the default body of the resume";
this.title="This is the default Title";

}


public decimal resumeid
{
get { return resumeid1; }
set { this.resumeid1=value; }
}
public decimal userid
{
get { return userid1; }
set { this.userid1=value; }
}
public string body
{
get { return body1; }
set { this.body1=value;}
}
public string title
{
get { return title1; }
set { this.title1=value; }
}


}//RESUME对象结束
}//DotNetRemoteTest名字空间结束

服务器端代码:
using System;
using System.Runtime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Data.SqlClient;
using DotNetRemoteTest;


namespace ResumeSuperServer
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class ResumeSuperServer
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此处添加代码以启动应用程序
//
HttpServerChannel channel = new HttpServerChannel(9932);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ResumeLoader),"ResumeLoader", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press Any Key");
System.Console.ReadLine();
}
}
}

客户端代码:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using DotNetRemoteTest;


namespace ResumeClient
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class ResumeClient
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此处添加代码以启动应用程序
//
ChannelServices.RegisterChannel(new HttpClientChannel());
ResumeLoader loader = (ResumeLoader)Activator.GetObject(typeof(ResumeLoader), "http://202.113.96.37:9932/ResumeLoader");
if(loader==null)
{ Console.WriteLine("Unable to get remote referance"); }
else
{
Resume resume = loader.GetResumeByUserID(2);
Console.WriteLine("ResumeID:"+ resume.resumeid);
Console.WriteLine("UserID:"+ resume.userid);
Console.WriteLine("Title:"+ resume.title);
Console.WriteLine("Body:"+ resume.body);

}
Console.ReadLine();//在能够看到结果前不让窗口关闭
}
}
}

数据库结构:
Resume
  ResumeID, numeric (autonumber)
  UserID, numeric
  Title, Char(30)
  Body, Text


  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-07-28 12:14 casual
关于“注册多个通道”,
“这个时候,我们必须用到System.Collection中的IDictionary接口:”

有更简单的方法,如下:
TcpServerChannel channel = new TcpServerChannel(string applicationname,int port);
Http通道也有相应的构造函数直接指定Name属性。


最后感谢作者,你的这几篇文章就是我的Remoting入门教程!
谢谢

  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-09-05 16:49 游客
wayfarer解答问题的诚意令人敬佩
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-09-15 00:44 yicone
to city:
to p:
sqlParameter没有被串行化

to py:
好像只能自己改造了,你还是问蝈蝈吧

to form:
让你的form类继承自MarshalByRefObject, 使用没有参数的构造函数,(一定要使用的话,使用客户端激活方式, 这时客户端的代码使用Activator.CreateInstance()能够接收参数数组的那个重载, 好像是上面的a);所有客户端向服务器传递的参数和方法调用的返回值必须可序列化!

to 阿扁:
文章中已经介绍过了
“由于服务器端和客户端都要用到远程对象,通常的方式是生成两份完全相同的对象Dll,分别添加引用。不过为了代码的安全性,且降低客户端对远程对象元数据的相关性,我们有必要对这种方式进行改动。即在服务器端实现远程对象,而在客户端则删除这些实现的元数据。”

to frank:
虽然不能解决你的问题,但还是想知道你使用的是客户端激活方式吗?
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-09-15 00:51 yicone
建议WayFarer大侠整理一下这里,文章加上评论太长了,我来回拖动鼠标几十次,好累啊!
幻想:要是有VS.NET中“ctrl" + "-"的功能就好了,或者把文章用#region #endregion按章节、小分类折叠起来就好了,目前好像不支持这俩个
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-09-15 09:47 游客
lovecherry文中说:
SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务
wayfarer文中说:
当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例

我感觉两句话意思差很远,除了自己动手试验外,哪个更接近真相?
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-09-18 13:24 feng
请教一下wayFarer大侠,一个.net Remoting支撑负载的问题。我们有个项目,有500个远程客户端通过1M 带宽ADSL和服务器端的中央数据库进行数据交换,每客户每分钟大概有一次调用。看了您的文章后,想采用.net Remoting 充当客户端和数据库的中间层,请问一下.net Reomoting能否支撑如此负载,假设中间层服务器的配置是PIV 2.8, 1024M RAM;以及采用哪种方式比较好,single call,工厂模式?最好服务器端能控制住同时能有多少个用户连接上来,对用户请求进行排队。
谢谢!
  

# re: Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 2005-10-15 22:33 奇思软件
问一个搞笑的问题,如果我在服务器端注册了singleton模式的远程对象,这意味着,所有客户端的调用都会调用同一个对象,并且它会维持状态,但有很多情况下,我在服务器端也要对这个对象进行一些操作,那么我该如何得到这个唯一对象的引址呢?

就拿典型的事件通知模型来说,我在服务器端有一个EventServer对象,我在每一个客户端放一个EventClient对象,这两个对象都是远程对象,并且EventServer为singleton模式的对象,当EventClient对象声明时会把自身的一段代码注册到EventServer的一个多点代理(事件)上,这样当有事件触发时,会调用这段代码,但有时,我想在服务器端也有这样的事件处理代码,这时最好的方式当然是我有EventServer的引址,直接进行事件注册,而由于不知道如何获取这个引址,所以目前我在服务器端也生明一个EventClient,并远程注册到本地机器上的服务器上,虽然能完成同样的功能,但总感觉不是十全十美,必竞在同一台电脑上我也通过远程的方式相互通信,不知你有什么建议?