您的位置:首页 > 其它

Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇

2010-11-15 20:29 387 查看
Microsoft .Net 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“终不悔”,方才可以“蓦然回首”吧。




张逸
关注 - 13
粉丝 - 222

荣誉:推荐博客
关注博主

10
0
(请您对文章做出评价)

« 上一篇:品味.NET经典[转载]
» 下一篇:在Visual C# .Net 中怎样向Microsoft Excel 2002 传输XML数据[译]

posted on 2004-07-30 20:44 张逸 阅读(85465) 评论(159) 编辑 收藏 所属分类: .NET Remoting



评论

评论共2页: 上一页 1 2

#45楼 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

}
 回复 引用 

#46楼 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 )时竟会出现异常---索引超出数组界限!
这个是不是跟传参编组有关?我真的很迷惑

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

#47楼 2004-11-26 18:05 linaren

这个类也加了序列化属性  回复 引用 

#48楼[楼主] 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。
 回复 引用 查看 

#49楼 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体系结构的可行性与实际的技术点探索,我以前对这的确是知之甚少-_-,现在发现这其中的东西多着呢!)

 回复 引用 

#50楼 2004-11-29 10:09 linaren

:)
===“索引超出数组界限”=====
这个问题我自己也正查看是什么原因的
====================
这个是我自己搞错了,引用了在Hashtable中不存在的key  回复 引用 

#51楼 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 。这样便于区别接口类型和类类型。因为,我发现你在写的时候,你常常把这两种对象弄混淆。  回复 引用 

#52楼 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. :) 希望你能给写个注册多个对象的示例代码,我学习一下,看是那里的问题。  回复 引用 

#53楼 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。如果有误的话,我想等我回去后,给你一个测试程序,你再看看。我还是肯定一点,注册多个对象是绝对没有问题的:)
 回复 引用 

#54楼 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错误了。

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

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

 回复 引用 

#55楼 2004-11-30 15:39 wayfarer

呵呵,解决了就好。遗憾的是我在给你回复的时候,客户端调用Activator.GetObject()方法也忘了写URI,呵呵,真是惭愧。

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

#56楼 2004-12-03 10:04 linaren

谢谢你
我以后有问题可能还要麻烦你的 :)
这两天我正搭建试验系统原型
知识可能是具有很强的繁衍性吧,由一个头开始我发现引发了很多东西的呵呵  回复 引用 

#57楼 2004-12-06 20:04 roky

不错,总结的很好  回复 引用 查看 

#58楼 2004-12-22 18:43 cenci

写的不错,多谢
请问SingleTon 和SingleCall是一次实例化一个对象还是可以实例化对象。(象EJB中SessionBean?)  回复 引用 

#59楼[楼主] 2004-12-22 21:31 wayfarer

SingleCall是在客户端每调用一次方法就实例化对象,之后马上销毁。而SingleTon则自始自终只实例化一个对象。
 回复 引用 查看 

#60楼 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.

很是郁闷,不知到原因出在哪里。  回复 引用 

#61楼 2005-02-18 21:52 heping

写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。

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

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

#62楼 2005-02-18 21:53 heping

写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。

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

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

#63楼 2005-02-18 21:53 heping

写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。

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

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

#64楼[楼主] 2005-02-19 21:24 wayfarer

@heping:
如果模块较多,最好不要采用替代类的方法。这样会让添加很多工作量。建议使用工厂方法,以WellKnown方式模拟客户端激活。

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

#65楼 2005-02-21 10:24 frank

为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”  回复 引用 

#66楼 2005-02-21 10:35 frank

为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”  回复 引用 

#67楼 2005-02-21 10:35 frank

为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”  回复 引用 

#68楼 2005-02-21 10:35 frank

为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”?  回复 引用 

#69楼 2005-03-14 13:39 city

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

#70楼 2005-03-29 13:58 Phoenix

我也在学习Remoting.看到作者这么热心的解答问题,真是让人感动.  回复 引用 

#71楼 2005-03-31 11:12 skywood

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

#72楼 2005-04-20 22:46 form

在remoting中如何传递一个form呢?  回复 引用 

#73楼 2005-05-04 17:24 阿扁

为什么要在客户端也要一个dll啊,感觉绕了一个圈.
 回复 引用 

#74楼 2005-05-21 09:56 pdy

蝈蝈俊的文章我也看了,解决了不少问题。可是还有问题!
你有没有尝试过在通道中传输SqlParameter??
我找了整个网络也没找到解决方法
现在报的错是:
权限被拒绝: 无法远程调用非公共或静态方法  回复 引用 

#75楼 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();
}
}

其他的都没问题  回复 引用 

#76楼 2005-05-25 16:58 留心

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

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

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

你的文章写的真不错
 回复 引用 

#77楼 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

 回复 引用 

#78楼 2005-07-28 12:14 casual[未注册用户]

关于“注册多个通道”,
“这个时候,我们必须用到System.Collection中的IDictionary接口:”

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

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

#79楼 2005-09-05 16:49 游客[未注册用户]

wayfarer解答问题的诚意令人敬佩  回复 引用 

#80楼 2005-09-15 00:44 yicone

to city:
to p:
sqlParameter没有被串行化

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

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

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

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

#81楼 2005-09-15 00:51 yicone

建议WayFarer大侠整理一下这里,文章加上评论太长了,我来回拖动鼠标几十次,好累啊!
幻想:要是有VS.NET中“ctrl" + "-"的功能就好了,或者把文章用#region #endregion按章节、小分类折叠起来就好了,目前好像不支持这俩个  回复 引用 查看 

#82楼 2005-09-15 09:47 游客[未注册用户]

lovecherry文中说:
SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务
wayfarer文中说:
当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例

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

#83楼 2005-09-18 13:24 feng[未注册用户]

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

#84楼 2005-10-15 22:33 奇思软件[未注册用户]

问一个搞笑的问题,如果我在服务器端注册了singleton模式的远程对象,这意味着,所有客户端的调用都会调用同一个对象,并且它会维持状态,但有很多情况下,我在服务器端也要对这个对象进行一些操作,那么我该如何得到这个唯一对象的引址呢?

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

#85楼 2005-12-01 10:28 kawashima[未注册用户]

你好,刚学.Net Remoting,我们现在需要在应用程序域中传递DataSet对象,请问如何序列化DataSet对象啊,期待你的回复,我现在非常急,谢谢!  回复 引用 

#86楼 2006-03-21 16:10 毁于随[未注册用户]

@奇思软件
不知道使用Marshal的方法能不能实现你的要求呢?  回复 引用 

#87楼 2006-04-17 17:18 passing traveller

文章真的写的不错,也和上面的有同感,最近开始学Remoting,你的这些文章也是我的入门文章!
谢谢了!
回复 引用 查看 

#88楼 2006-04-29 14:51 Aadon[未注册用户]

汗...
偶是菜鸟,看的蒙蒙的..
学Remoting 前要先学什么?  回复 引用 

#89楼 2006-05-30 15:46 xietangzcn[未注册用户]

"可以将Remoting放到IIS中以Web Service的方式进行。"-->我正写此程序,请教作者如何处理! 即通过WEB SERVICE的方式如何调用REMOTING,能否给个例子!

谢谢!

顺向作者问好!  回复 引用 

#90楼 2006-05-31 14:39 levinknight

用Marshal()方法就意味着是Singleton,那用SingleCall激活的对象呢,要怎么关闭呢?  回复 引用 查看 

#91楼 2006-06-07 13:23 forair[未注册用户]

我也在学习REMOTING中,不过刚学的
我想问一下,我在客户端想传一个自己定义的对象到服务端的时候它会弹出一个异常说:
由于安全限制,无法访问类型 System.EnterpriseServices.ServicedComponentMarshaler。

我上网查了一下也查不到关于ServicedComponentMarshaler的资料,
我的那个对象已经标明的[Serializable],并且那个对象我是做成一个组件的,我在客户端和服务端也引用的它的DLL了,请大家指教一下,  回复 引用 

#92楼 2006-06-07 21:46 forair[未注册用户]

现在那个ServicedComponentMarshaler解决了,不过它现在又弹出一个异常说:
由于安全限制,无法访问类型Command.Customer(这个类是我自己定义的,是用来保存Customer信息的)
真的很郁闷啊,搞了很久啦,都还是没有解决啊。。。。。。不懂啊。。

我的项目:
服务端:
一个是Command(类库),我把它做成一个组件。里面有一个Customer.cs
一个是OfficeMartLib(类库),我也把它做成一个组件。里面有一个OfficeMartLib.cs这个里面定义了一些方法是在服务端运行的,其中一个是用来接收客户端发来的对象,就是那个Customer.
最后一个就是OfficeMartService(一个Window服务)

客户端:
就一个就是写了个一OfficeMartClient (一个Window应用程序)

我这个东东主要想做的就是在客户端通过远程服务OfficeMartService调用组件发送一个对象回服务端

因为正在学习服务组件很多东西都不了解,希望各位大哥指一下啊,
 回复 引用 

#93楼 2006-08-09 00:46 dddang[未注册用户]

请问一个问题,如果我在服务端有多个remote object需要export,那么RegisterChannel应该怎么调用呢,换句话说是不是服务端只需要调用一次RegisterChannel,然后所有要export的remoteobject都调用RegisterWellKnownServiceType就可以了么,是不是说多个remote Object共用一个前面注册的channel端口呢??

上述情况是指所有Remote Object都存在于同一个Server程序中.
class A:MarshalByRefObject;
class B:MarshalByRefObject;

class server{
server(){
ChannelService.RegisterChanell(new TcpChannel(9999));
RegisterWellKnownServiceType(typeof(A),"A");
RegisterWellKnownServiceType(typeof(B),"B");
}
}

我注意到RegisterChannel是个static函数,那么RegsiterWellKnownService怎么知道注册的object应该用哪个Channel呢
问了那么多,也不知道描述清楚没有  回复 引用 

#94楼 2006-10-10 10:13 feng[匿名][未注册用户]

帮我看看这是哪出错了,我用的是framework2.0
Activated模式
运行客户端出现异常 :类型没有为激活而注册
服务端:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel,true);
RemotingConfiguration.RegisterActivatedServiceType(typeof(Customer));
客户端:
TcpChannel chnl = new TcpChannel();
ChannelServices.RegisterChannel(chnl,true);
RemotingConfiguration.RegisterActivatedClientType(typeof(Customer), "tcp://localhost:8080");
Customer cust = new Customer("Homer");  回复 引用 

#95楼 2006-10-26 15:43 laugha[未注册用户]

有关“Remoting要求必须是引用的对象,所以必须将Person类序列化”这句,不大理解,所有类都必须序列化吗?如果不序列化PERSON类的话,客户端调用时也可以取到PERSON类的属性值阿?
服务端:
public DataEntity.UserInfo test()
{
DataEntity.UserInfo uu=new DataEntity.UserInfo();
uu.userName="张三";
uu.userPwd="dfsdf";
return uu;
}

客户端:(采用TRICK类)
obj = new RemotingServerObject.AutoServiceGetDataSet();
Console.WriteLine( "Client tcp {0}",obj.test().userName);
也可以得到"张三"的阿??
请问各位我是哪里理解错了??谢谢大家。  回复 引用 

#96楼 2006-10-28 15:51 海边拾贝的流浪者[未注册用户]

学习NET REMOTING 遇到这么多问题 真的郁闷哦
现在的问题是 服务端
- System.SystemException {"指定的转换无效。"} System.SystemException  回复 引用 

#97楼 2006-11-18 10:58 Fox[匿名][未注册用户]

近來用Remoting做中間層服務器,採用服務端激活方式(客戶端激活時問題同樣存在),從服務端遠程對象取數據正常,但是在客戶端給遠程對象屬性賦值後,在服務端跟蹤調試發現服務端取不到客戶端賦給遠程對象的值(但在客戶端賦值後再取其屬性值可以取到),因為採用的是TCP協議通信,因為Remotin Server 底層通信採用的是Socket,所以感覺是數據隻能單向傳輸,即隻能從服務端接收數據,而不能傳送更新的數據給服務端,請問高手,是因為通道創建不對還是其它原因造成的!非常感謝!
注:我的服務端對象有從MarshalByRefObject繼承,不過沒有用 [Serializable]屬性
服務端通道注冊代碼為:
Hashtable TcpPort = new Hashtable();
TcpPort.Add("port", 3119);

Hashtable HttpPort = new Hashtable();
HttpPort.Add("port", 4119);

BinaryServerFormatterSinkProvider SrvProvider = new BinaryServerFormatterSinkProvider();
SrvProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

BinaryClientFormatterSinkProvider ClientProvider = new BinaryClientFormatterSinkProvider();

TcpChannel chan = new TcpChannel(TcpPort, ClientProvider, SrvProvider);
HttpChannel chan2 = new HttpChannel(HttpPort, null, SrvProvider);

ChannelServices.RegisterChannel(chan);
ChannelServices.RegisterChannel(chan2);

RemotingConfiguration.RegisterWellKnownServiceType(typeof(ClassFactoryObj), "St", WellKnownObjectMode.SingleCall);

客戶端通道注冊代碼為(不注冊一樣可以通信):

//注冊通道
TcpChannel TcpChan = new TcpChannel();
ChannelServices.RegisterChannel(TcpChan);

//仿客戶端端激活方式
PUBFac = (TPUBClassFactory)Activator.GetObject(typeof(TPUBClassFactory), "tcp://10.168.0.230:3119/St");
Stor = PUBFac.CreateStorageObj();
 回复 引用 

#98楼 2006-11-18 16:51 Fox[匿名][未注册用户]

謝謝各位,這個問題已經解決了,主要是對Remoting的工作原理了解不是很深,不過今天還是突然想到了,呵呵  回复 引用 

#99楼 2006-12-19 18:11 kylin[匿名][未注册用户]

好文章!学习过后,受益非浅!谢谢啊,呵呵,继续努力,多出好文章!!  回复 引用 

#100楼 2007-01-16 16:35 小鱼[未注册用户]

好文章,非常感谢!  回复 引用 

#101楼 2007-01-24 17:46 来宾[未注册用户]

DataSet已经序列化了,就不用序列化了;
另外想问个问题:
既然把Person类做成dll放在客户端和服务器端,那还用remoting干什么,客户断直接调用自己的就可以了吧。  回复 引用 

#102楼 2007-01-26 14:24 天外飞仙[未注册用户]

看了几遍,每次的收获都不同啊!!  回复 引用 

#103楼 2007-02-06 11:57 PIAO[未注册用户]

请楼主帮忙看下~~
服务启动时做:
private TcpChannel chan = new TcpChannel(3333);
ChannelServices.RegisterChannel(chan, false);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CacheClass.CacheClass),
"Cache", WellKnownObjectMode.SingleCall);
运行正常;
可是改成以下配置并启动时做:
RemotingConfiguration.Configure("HaulageService.config",false); 服务就启动不了了!!!
<configuration>
<system.runtime.remoting>
<application name = "HaulageService">
<service>
<wellknown mode="SingleCall" type="CacheClass.CacheClass" objectUri="Cache"/>
</service>
<channels>
<channel ref="tcp" port="3333"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
以上步骤跟楼主说的没差别呀,配置文件也放在服务安装的文件夹
服务器端读取配置文件还需要注意什么呢??
为什么一改成配置,服务(Win服务)就启动不了? 配置的问题?
 回复 引用 

#104楼 2007-02-10 13:36 一凡[未注册用户]

楼主你好:第一次访问你的博客,看了你的文章之后,感觉受益非浅,
但是有一点我不明白,我们不是可以直接用ADO.net可以访问吗!!干嘛这么麻烦呀  回复 引用 

#105楼 2007-03-05 10:12 zengchumin[未注册用户]

在網上其他地方也看過這篇文件
樓主只是轉過來而已吧???  回复 引用 

#106楼[楼主] 2007-03-05 11:01 Bruce Zhang

@zengchumin
气煞我也,我自己的原创,居然有人诬蔑我是转载。拜托你在发表评论之前,先仔细看看。看清楚我发表此文的时间,看看大家的评论。

坦白说,我这篇文章在发表之后,可以说被转载的次数太多了,我也懒得理睬。可如今却因为看了别处的转载文字,反过来说我的原创是转载,真是让我哭笑不得啊。 回复 引用 查看 

#107楼 2007-03-22 15:10 eros

Bruce,你好,最近项目中想使用remoting技术,正好拜读了你的文章,让我收获颇丰,在此表示感谢。只是实践中遇到一点问题,在文章第五部分 remoting基础的补充的2远程对象的元数据相关性中,关于客户端激活中用WellKnown激活模式模拟客户端激活的方法,我按照文中所述实验了一下,写了如下代码:

服务端远程对象:
//抽象工厂接口
public interface IObjectFactory
{
IRemoteObject CreateInstance();
}
//抽象工厂实现
public class ObjectFactory : MarshalByRefObject, IObjectFactory
{
public ObjectFactory()
{
}

public IRemoteObject CreateInstance()
{
return new RemoteObject();
}

//远程对象接口
public interface IRemoteObject
{
string TempTransfer(float temp, string t);
}

//远程对象实现
public class RemoteObject : MarshalByRefObject, IRemoteObject
{
public string TempTransfer(float temp, string t)
{
//......
}
}

注册远程对象
TcpChannel tcpChannel = new TcpChannel(8080);
ChannelServices.RegisterChannel(tcpChannel);
RemotingConfiguration.RegisterWellKnownServiceType(typeof (ServerRemoteObject.ObjectFactory), "ServiceMessage", WellKnownObjectMode.SingleCall);

客户端
tcpChannel = new TcpChannel();
ChannelServices.RegisterChannel(tcpChannel);

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

remoteObject = objectFactory.CreateInstance();*

问题就出在标星号的这一句上,启动服务端后在启动客户端,运行到这一句就产生异常“未处理的“System.InvalidCastException”类型的异常出现在 mscorlib.dll 中。其他信息: 返回参数具有无效的类型。”,百思不得其解,不知是哪出错了呢?还望解答,先谢过。
 回复 引用 查看 

#108楼[楼主] 2007-03-22 16:02 Bruce Zhang

@eros
我没有看到客户端代码中remoteObject实例的定义。此外,你的IRemoteObject接口的定义程序集被部署到了客户端了吗?  回复 引用 查看 

#109楼 2007-03-22 19:58 eros[未注册用户]

是否在服务端的远程对象中提供工厂和远程对象的接口以及它们的完整定义,而在客户端的远程对象中仅仅提供工厂和远程对象的接口? 程序集在服务端和客户端都分别部署了.如果方便的话我把完整的代码发给你帮我看看,卡在这却不知何解心里真不好受.  回复 引用 

#110楼 2007-04-23 15:57 Nina

有沒remoting應用在web的啊。我現在看到的都是win方面的  回复 引用 查看 

#111楼 2007-04-26 11:18 kevin[未注册用户]

好文章,受益非浅!  回复 引用 

#112楼 2007-04-26 17:56 小杨[未注册用户]

文章很好啊,谢谢,还有关于这样的资料吗??我的E_mail是web_asp.net@163.com
 回复 引用 

#113楼 2007-04-27 10:33 liuhui2082@163.com [未注册用户]

今天才开始看remoting 感觉好抽象。
要求能调用另一台电脑上的程序或进程。
请问有没好的思路啊?
或者有好的代码等就感激不尽...
代码:liuhui2082@163.com 并请留言说明下 谢谢  回复 引用 

#114楼 2007-05-10 15:43 在北京的湖南人

一直不明白为什么需要把远程对象程序集服务器端和客户端都需要布置?还是客户端只需要类似ws那样的代理类的具体远程对象信息就可以了呀?那样的话,那我们是不是可以根据接口来编程呢? 还请指教!  回复 引用 查看 

#115楼 2007-05-14 23:44 fee[未注册用户]

good  回复 引用 

#116楼 2007-06-11 10:34 andyli[未注册用户]

不错,写得非常的易懂而不切深奥,学习Ing。。。  回复 引用 

#117楼 2007-07-10 16:42 一凡[未注册用户]

请问一下水晶报表怎么调用 呀  回复 引用 

#118楼 2007-07-31 13:48 Kylin[未注册用户]

您好,这是我第二次拜读您的文章,第一次有些囫囵吞枣,第二次读反而有了些问题,其他人也许也曾提到过,不知道现在是否已经解决了.
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerRemoteObject.ServerObject),
"ServiceMessage",WellKnownObjectMode.SingleTon);
其实就是多通道的问题,假如我采用服务端激活但是不管哪种方式,如果我创建了多个通道,那么我的远程对象在注册的时候是如何确定注册到哪个通道上呢,比如例子中的httpchanel和tcpchanel,那么现在我有
serverobj1,serverobj2,在将这两个对象用RemotingConfiguration.RegisterWellKnownServiceType进行注册的时候如何指定注册到哪个通道(chanel)??,如果只有一个,我发先就是我所建立的第一个通道,如果没有正如其他人所说的是machine.config当中的默认通道,但是现在有多个通道,不知道是如何解决的呢??看了上面的文章似乎没有提及到服务器对象在多通道注册的问题啊,谢谢!!!十分关注remoting的应用,因为在delphi里发现对于负载均衡的实现想比要简单,但是在.net下实在没有发现类似dcom这种可以做为appserver的方式,故对次问题十分关注,期待您的答复!!  回复 引用 

#119楼 2007-08-04 12:53 毛毛虫[未注册用户]

你好,我正在学习remoting.
用的《c#高级编程》,有个问题亟待解决。
我在客户端有多个对象的透明代理,这些对象实际上都在服务器端。我想完全像使用本地对象一样在客户端使用这些透明代理来使用服务器上的真正对象。

当我在客户端调用其中一个透明代理的方法,这个方法以另一个透明代理作为参数。这样编译没有问题,调试起来就报异常:Because of security restrictions, the type System.Runtime.Remoting.ObjRef cannot be accessed.
我尝试过把作为参数的那个类去掉对System.MarshalByRefObject的继承,而在其前声明[Serializable],这样没有异常了,但是这样该后,对象就被传送到了客户端,而不是对服务器端对象的引用了。这样做会产生其他问题。

这中问题您也应该遇到过吧,请问怎么解决的?

另外,我学习这个每次都要开3个vs,一个客户端,一个服务器端,还有一个做dll程序集。每次dll文件一改动,客户端和服务器端的引用都得删了重新添加,很麻烦,而且最大的问题是做成带来了dll文件的类不能调试。
vs这么强大,应该不会有这种问题吧,我该怎么用vs做这个?
 回复 引用 

#120楼 2007-08-04 13:00 毛毛虫[未注册用户]

而且客户端和服务器端都同时引用相同的dll文件,这样是不是太冗余啊?

在我的理解中,c#的remoting及其类似java的rmi。
而java中的rmi在客户端好像只放个类的接口就想了,真正实现只要放在服务器端。  回复 引用 

#121楼 2007-08-04 15:29 毛毛虫[未注册用户]

知道怎么在一个vs中用多个工程了。
 回复 引用 

#122楼 2007-08-14 15:47 lsl[未注册用户]

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

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

remoteObject = objectFactory.CreateInstance();

这段代码中注册的通道没有体现出被使用阿?请大哥解释一下,这个注册通道到底有用吗?如果有用,是怎么被使用的呢?谢谢。  回复 引用 

#123楼 2007-08-21 17:44 在线代理[未注册用户]

@ Nina

在web方面 ,估计都是用的web service了。何必这么麻烦呢  回复 引用 

#124楼 2007-08-23 16:44 superstar

请问我这怎么错误的
无法启动服务。System.Security.SecurityException: 不允许所请求的注册表访问权。
at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
at System.Diagnostics.EventLog.FindSourceRegistration(String source, String machineName, Boolean readOnly)
at System.Diagnostics.EventLog.SourceExists(String source, String machineName)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type)
at ghyWindowsService.ghyService.OnStart(String[] args) in e:/test/ghyremotingtest/ghyremoting/ghywindowsservice/ghyservice.cs:line 140
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)

有关更多信息,请参阅在 http://go.microsoft.com/fwlink/events.asp 的帮助和支持中心。  回复 引用 查看 

#125楼 2007-12-13 01:00 kao720[未注册用户]

請問, 能不能走tcpChannel, Server使用Windows Form, Client使用Web Form.  回复 引用 

#126楼 2007-12-14 15:16 强强

从一开始就已经把你当作我的偶像,有太多的不明白想求教,但我的理解能力很明显是有限的,只能通过时间来打通任督二脉.
昨天终于鼓起勇气在博客园注册了个号,希望以后能得到您的指点.  回复 引用 查看 

#127楼 2008-06-03 20:29 xyzremoting[未注册用户]

请问一下楼主:
在"Remoting基础的补充"-->(2) 客户端激活模式:中提到的
a、利用WellKnown激活模式模拟客户端激活模式
如果在配置文件中同时配置服务器端和客户端,由于接口不能被实例化,如何在客户端引用呢:
这是服务器端的配置:
<system.runtime.remoting>
<application name="ServerRemoting">
<service>
<wellknown mode="SingleCall" type="ServerRemoteObject.ServerObjectFactory,RemoteObjectApplication" objectUri="ServiceMessage"/>
</service>
<channels>
<channel ref="tcp" port="8080"/>
<channel ref="http" port="8180"/>
<channel ref="ipc" portName="RemotingIPCPort"/>
</channels>
</application>
</system.runtime.remoting>

这是客户端端的配置:
<system.runtime.remoting>
<application name="ClientRemoting">
<client>
<wellknown type="ServerRemoteObject.IServerObjectFactory,ServerWindowsApplication" uri="http://localhost:8080/IServerObjectFactory/ServiceMessage"/>
</client>
<channels>
<channel ref="http" port="8180"/>
</channels>
</application>
</system.runtime.remoting>

客户端引用:
private void FrmMain_Load(object sender, EventArgs e)
{
RemotingConfiguration.Configure("ClientWindowsApplication.exe.Config", false);
IServerObjectFactory pServerObjectFactory = new *****();//有问题,由于客户端没有ServerObjectFactory类,而只有IServerObjectFactory类
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: