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“终不悔”,方才可以“蓦然回首”吧。
posted on 2004-07-30 20:44 Bruce Zhang 阅读(37063) 评论(136) 编辑 收藏 所属分类: .NET Remoting
评论
文章真不错!
你的图画得真漂亮,是用什么画的? 回复 引用 查看
#2楼 [未注册] 2004-07-31 10:48 wayfarer
可不是我画的,在网上截的图,嘿嘿:) 回复 引用 查看
#3楼 [未注册] 2004-07-31 16:42 吕震宇
在微软的“企业模式”中好像看到过一些图和内容。我正在酝酿一篇《用Remoting技术传递Event》(题目还没有想好,可能还会改),有了这篇文章,看来可以省不少字了。 回复 引用 查看
#4楼 [未注册] 2004-08-09 15:09 DYFILE
好文章。
再来两篇。。。。 回复 引用 查看
#5楼 [未注册] 2004-08-12 15:50 游客
不错,我有个问题,说起来丢人。
看以前的代码,我只是用了TcpChannel tcp = new TcpChannel();来声明一个tcp通道,并没有显示的注册,但是程序也能跑,这是为什么?系统会自动注册吗? 回复 引用 查看
#6楼 [未注册] 2004-08-12 15:53 wayfarer
你是指不通过ChannelServices.RegisterChannel()方法来注册吗? 回复 引用 查看
#7楼 [未注册] 2004-08-12 16:03 wayfarer
你是指不通过ChannelServices.RegisterChannel()方法来注册吗?这个问题比较有趣!我测试了一下,如果你在服务器端没有通过RegisterChannel()方法显式注册,在运行服务器端时并没有错误;但如果你在客户端去激活服务器端对象,则会抛出异常:“此远程处理代理没有信道接收,这意味着服务器没有正在侦听的已注册服务器信道,或者此应用程序没有用来与服务器对话的适当客户端信道。”说明该通道不能使用。
不过如果在客户端不注册通道,对Remoting没有影响。但我建议你最好在构造通道之后,对他进行注册。否则,当你注销通道时,也许会发生不可预料的错误。 回复 引用 查看
#8楼 [未注册] 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 .... 回复 引用 查看
#9楼 [未注册] 2004-08-12 17:02 wayfarer
同意csl,对于端口号而言,在服务器端构造通道时,通常应该指定具体的端口。如果端口号不确定,可以在构造函数中使用0。但它表示的并非端口号为0,而是指系统随机选择端口。但由于我们在客户端激活服务对象时,必须指定具体的uri。而这个uri有一个重要的组成部分就是端口号。如果采用随机端口号,客户端无法捕捉。
不过在客户端构造了端口之后,虽然可以不用再注册通道,但我还是建议应该注册。
@csl
我在我的英文博客中整理了对Remoting通道占用的讨论,不知道是否正确,你可以去看看:)
http://dotnetjunkies.com/WebLog/wayfarer/ 回复 引用 查看
#10楼 [未注册] 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)
回复 引用 查看
#11楼 [未注册] 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?
回复 引用 查看
#12楼 [未注册] 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...)
回复 引用 查看
#13楼 [未注册] 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
回复 引用 查看
#14楼 [未注册] 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. 回复 引用 查看
#15楼 [未注册] 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 ..... 回复 引用 查看
#16楼 [未注册] 2004-08-12 18:58 wayfarer
:)
me too. 回复 引用 查看
#17楼 [未注册] 2004-09-06 13:47 buaaytt
这种方法能再讲详细么?比如具体初始化远程对象和调用代码该怎么写呢?我现在就需要带参数的构造函数
另外:采用服务端还是客户端激活?通常是怎么选择的呢?
回复 引用 查看
#18楼 [未注册] 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模式。至于客户端激活模式,则是在客户端激活对象时,该对象就产生了,你可以管理它的生命周期。 回复 引用 查看
#19楼 [未注册] 2004-09-06 16:09 longsan
provider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
因为.net1.1的安全性 回复 引用 查看
#20楼 [未注册] 2004-09-07 12:15 wayfarer
@longsan
你是指什么呢?
回复 引用 查看
#21楼 [未注册] 2004-09-07 21:37 wlz
能用
serverobject 访问 server端的其他对象吗?
比如我用form启动,加载serverobject,
当客户端调用时,调用serverobject,但是消息不是回显,而是显示在server的form上? 回复 引用 查看
#22楼 [未注册] 2004-09-08 12:33 wayfarer
作为ServerObject,在其方法内部所调用的对象都是服务器端对象,只有传递的参数和返回值会完成和客户端的通讯。前提是参数值和返回值是可序列化的。
因此在客户端调用服务对象时,如果用MessageBox显示信息的方法是放在服务对象中,则该消息会显示在服务器端。 回复 引用 查看
#23楼 [未注册] 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 ??
回复 引用 查看
#24楼 [未注册] 2004-09-27 15:49 landyz
把BITMAP序列化成流,问题解决 回复 引用 查看
#25楼 [未注册] 2004-10-09 11:33 skywood
文中说道:
在“模拟的SingleTon”模式中,我们是将具体的远程对象实例进行Marshal,以此让客户端获得该对象的引用信息。那么我们换一种思路,当我们用抽象工厂提供接口,工厂类实现创建远程对象的方法。然后我们在服务器端创建工厂类实例。再将这个工厂类实例进行Marshal。而客户端获取对象时,不是获取具体的远程对象,而是获取具体的工厂类对象。然后再调用CreateInstance()方法来创建具体的远程对象实例。此时,对于多个客户端而言,调用的是同一个工厂类对象;然而远程对象是在各个客户端自己创建的,因此对于远程对象而言,则是由客户端激活,创建的是不同对象了。
=============
可是这样以来,客户端又如何对各自远程对象的生命周期进行管理呢?这样通过工厂类生成的远程对象是否也能参与它的生命周期管理呢?能详细说明一下吗? 回复 引用 查看
#26楼 [未注册] 2004-10-09 17:47 wayfarer
首先工厂对象的生命周期可以通过重写InitializeLifetimeService()法方法,设定其生命周期的值。至于通过工厂类创建的远程对象,由于其同样派生自MarshByRefObject,因此仍然可以重写InitializeLifetimeService()方法来管理。因为虽然这个对象是在客户端激活创建的,但该对象仍然创建在服务器端,因此管理的方式是一致的。
回复 引用 查看
#27楼 [未注册] 2004-10-09 19:46 Bigbigpoo
请问能不能使用客户端到Web应用程序的访问呢?
我在Web.config里配置了文中写的XML配置内容。但是,使用TCP通道的时候,在客户端就是找不到服务器端。
有没有使用TCP通道,并且是WellKnown激活模式的客户端和Web.config的例子呢?
谢谢! 回复 引用 查看
#28楼 [楼主] 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的经验。
所以,可能需要查阅一些资料,才能知道答案。很抱歉现在还无法回答你的问题。
回复 引用 查看
#29楼 [楼主] 2004-10-10 19:50 wayfarer
@Bigbigpoo
一节写到节点中就可以了。请注意大小写。
首先应该承认我的错误。因为没有在ASP.NET下部署过Remoting,所以在上面给你的答复中出现了错误。
事实上,使用ASP.NET使用Remoting是很常见的一种用法,此时ASP.NET应用程序是作为Remoting的客户端,它通过Remoting去调用服务器端的服务。其实,这完全类似调用WebService,只是实现的方式不同而已。
我在文章中写到的配置文件应是正确的,不过在Web.config中应该是设置客户端。你只需要把
另外,如果你部署到IIS中,请注意消息名应加上后缀.rem或.soap。
回复 引用 查看
#30楼 [未注册] 2004-10-11 18:04 Bigbigpoo
能够使用TCP的Channel并且是使用WellKnown激活模式吗? 回复 引用 查看
#31楼 [未注册] 2004-10-28 16:47 sunhappy1
请问能不能使用客户端到Web应用程序的访问呢?
我在Web.config里配置了文中写的XML配置内容。但是,使用TCP通道的时候,在客户端就是找不到服务器端。
有没有使用TCP通道,并且是WellKnown激活模式的客户端和Web.config的例子呢?
谢谢!
如是是Web应用程序,部署到IIS上,一定要走HTTP通道! 回复 引用 查看
#32楼 [未注册] 2004-11-16 17:54 wangliangzhong
好文章,我们现在正在做一个pdm项目,准备采用微软的智能客户端技术实现,可能需要用到remoting,一个问题是大系统采用remoting,远程对象会不会过多. 回复 引用 查看
#33楼 [楼主] 2004-11-17 11:56 wayfarer
有这个问题。所以需要合理管理远程对象的生命周期。 回复 引用 查看
#34楼 [未注册] 2004-11-18 14:37 buaaytt
很久没来了,我上次问的如何选择激活模式的意思是:各种激活模式在性能等方面有什么差异?各自有什么优缺点?
知道了这个,我才好选择是使用哪种激活模式。而不是说简单地决定使用SingleCall还是SingleTon
另外:
能够讲一下远程对象生存期的管理吗?
对于不同的远程对象和激活模式,生存期管理是怎么一回事呢?
比如:从你的文章里只是简单说了SingleCall是由GC管理,客户端激活的则可以自定义生存期,那么SingleTon模式的呢? 回复 引用 查看
#35楼 [楼主] 2004-11-18 17:30 wayfarer
关于生命周期管理,请看我的这篇文章:《Microsoft .Net Remoting系列专题之一:Marshal、Disconnect与生命周期以及跟踪服务》
关于三种激活方式的区别,请看我的这篇文章:《Microsoft .Net Remoting系列专题之二:从实例谈Remoting的激活模式及相关技术》,当然这篇文章我还没写完。但应该可以解决你的问题了。 回复 引用 查看
#36楼 [未注册] 2004-11-24 15:10 linaren
我是带着问题搜了一下remoting就看到你的文章,内容很系统也很详细的,要是我能早些时候看到的话,有一些共性问题也就不花费那么长时间了,
现在我有个新的问题就是怎样在一个应用程序里注册多个远程对象?我查看了我有的资料都没能解决,
请教你一下,望不吝赐教,谢谢! 回复 引用 查看
#37楼 [未注册] 2004-11-24 18:26 wayfarer
谢谢linaren。在我的博客里,目前的Remoting知识还是一些基本的介绍,在博客园里,还有很多研究Remoting的,如Rickie,吕震宇等。你可以就在博客园里搜索。至于你的问题,怎样在一个应用程序里注册多个远程对象?
就我目前所知,注册多个远程对象完全没有问题的啊,实现的方法和注册一个远程对象的方法没有区别,只需要你所设置的url的message名设置成唯一的就可以了。 回复 引用 查看
#38楼 [未注册] 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
:)再次请教了
回复 引用 查看
#39楼 [未注册] 2004-11-25 14:28 channelV
关于未注册通道程序也会运行的解释:【摘自.net文档】注意 可能不需要在客户端上注册信道。如果客户端未注册信道,远程处理系统将使用在 Machine.config 文件中指定的默认信道之一自动选择或创建一个信道以发出传出的请求。客户端上的这一自动信道选择并不注册信道来侦听服务器中的任何回调函数,并且不注册任何自定义信道实现(除非该自定义信道已添加到 machine.config 文件中)。这些情况下,必须在客户端应用程序域中注册要使用的信道类型。 回复 引用 查看
#40楼 [楼主] 2004-11-25 21:01 wayfarer
@linaren:
从代码上来看,服务器端没有问题。当然你后来的说法让我迷惑。你说object1和object2都是interface,而如果是interface则不能继承MarshalByRefObject。不过你说object1调用正常,这排除了我的疑惑。
我的理解是object1和object2应该是两个类,他们在继承MarshalByRefObject,同时又分别实现接口。而在 客户端调用的时候,则生成两者的接口。因为在服务器端注册远程对象时,typeof获取的应该是类的类型,而非interface.
你能把object1和object2两个对象及客户端的代码贴出来。可能是这些代码有误。我想,看到较为完整的代码后,我可以给你一个好的解答。
有一点我可以肯定,在一个通道中绝对可以注册多个对象,因为我已经在项目中实施过了,而且这个Remoting远程对象管理器运行得很好。 回复 引用 查看
#41楼 [楼主] 2004-11-25 21:03 wayfarer
@channelV:
你的补充很正确。在客户端完全可以不用注册通道。如果确实要注册通道,也不用指定端口号。
至于通道类型,在客户端调用远程对象时,会自动判断通道的类型。 回复 引用 查看
#42楼 [楼主] 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()方法。
回复 引用 查看
#43楼 [未注册] 2004-11-26 17:40 linaren
谢谢你的解答
:)不好意思,我说错了,是这样的
object1与object2都是接口,实现object1与object2的都是继承于
MarshalByRefObject的 回复 引用 查看
#44楼 [未注册] 2004-11-26 17:51 linaren
这以具体的两个接口与实现
///
/// User interface
///
public interface UserPerform
{
DataTable GetUserList();
DataTable GetVipUserList();
string RegisterVipUser( User user );
string UnRegisterVipUser( User user );
string AlterPassWord( User user );
}//class interface
///
/// Goods interface
///
public interface GoodsPerform
{
DataTable GetAllGoods();
string AddGoods( Goods goods );
string DelGoods( Goods goods );
string ModGoods( Goods goods );
}
回复 引用 查看
#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楼 [未注册][TrackBack] 2005-01-28 16:09 pqzemily
Ping Back来自:blog.csdn.net
[引用提示]pqzemily引用了该文章, 地址: http://blog.csdn.net/pqzemily/archive/2005/01/28/272090.aspx 回复 引用 查看
#62楼 [未注册] 2005-02-18 21:52 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-18 21:53 heping
写了个remoting的项目,采用客户端激活,采用了:利用替代类来取代远程对象的元数据。
有个问题:
原来的三层结构,非常清楚简单。界面层 访问 应用层,应用层 访问 数据访问层。数据访问层与数据库通信。
现在,利用替代类,我新增了两个项目,分别是:客户端的RemoteObject项目,就是:空的远程对象的集合;服务端的RemoteObject项目,就是:实现了的远程对象的集合。
由于我的项目内容比较多,这每个小模块,都增加了这么两层的工作,汇总起来,工作量就直线上升。
请教,我现在的结构如下,正确么?
项目1:客户端:界面层,访问客户端的RemoteObject项目;
项目2:客户端的RemoteObject项目:把应用层的每个类的方法用空的实现,写一次;考虑到应用层的类的数目庞大,若干个小模块,放在一个对象中。但这样,方法名,要作修改;
项目3:服务端的RemoteObject项目:客户端的RemoteObject项目,每个方法,调用应用层的类的方法;应用层、数据访问层;
项目4:公共对象:存放客户端和服务端各层都共享的数据对象;
项目5:服务端:调用服务端的RemoteObject项目。 回复 引用 查看
#65楼 [楼主] 2005-02-19 21:24 wayfarer
@heping:
如果模块较多,最好不要采用替代类的方法。这样会让添加很多工作量。建议使用工厂方法,以WellKnown方式模拟客户端激活。
另外对于分布式开发,一定要注意方法。将接口单独放在一个程序集中,便于部署。 回复 引用 查看
#66楼 [未注册] 2005-02-21 10:24 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。” 回复 引用 查看
#67楼 [未注册] 2005-02-21 10:35 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。” 回复 引用 查看
#68楼 [未注册] 2005-02-21 10:35 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。” 回复 引用 查看
#69楼 [未注册] 2005-02-21 10:35 frank
为什么客户端异常退出后,再重新连接服务器就会报“基础连接已关闭,无法连接到远程服务器。”? 回复 引用 查看
#70楼 [未注册][TrackBack] 2005-02-25 08:16 LoveCherry
Ping Back来自:blog.csdn.net
[引用提示]LoveCherry引用了该文章, 地址: http://blog.csdn.net/lovecherry/archive/2005/02/25/301142.aspx 回复 引用 查看
#71楼 [未注册][TrackBack] 2005-02-25 08:16 LoveCherry
Ping Back来自:blog.csdn.net
[引用提示]LoveCherry引用了该文章, 地址: http://blog.csdn.net/lovecherry/archive/2005/02/25/301141.aspx 回复 引用 查看
#72楼 [未注册][TrackBack] 2005-03-05 17:11 philipsslg
Ping Back来自:blog.csdn.net
[引用提示]philipsslg引用了该文章, 地址: http://blog.csdn.net/philipsslg/archive/2005/03/05/312103.aspx 回复 引用 查看
#73楼 [未注册] 2005-03-14 13:39 city
我遇到一个问题就是:
客户端和服务器都是用配置文件配置的,server端直接调用RemotingConfiugre.config(配置文件)。然后一切正常,可是当客户端调用传入SqlParameter数组参数的时候异常:“此远程处理代理没有信道接收,这意味着服务器没有正在侦听的已注册服务器信道,或者此应用程序没有用来与服务器对话的适当客户端信道”。
而我改为string数组是没有问题的。可否指点一下? 回复 引用 查看
#74楼 [未注册] 2005-03-29 13:58 Phoenix
我也在学习Remoting.看到作者这么热心的解答问题,真是让人感动. 回复 引用 查看
#75楼 [未注册] 2005-03-31 11:12 skywood
其实采用替代类的方法也并不会增加多少工作量的,我没有怎么用过SoapSuds.exe这个工具,而且看他的命令行参数,感觉一不直观二也确实挺麻烦的。我在现在做的一个项目中就是自己来写这个生成器,虽然代码写的很烂,但也就百来行代码,用起来还可以,反正没出什么错,选中程序集就可以自动生成其中所有的替代类了。当然适用的情形可能都很简单,我的意思是实现这个生成器或者扩充功能并不难和麻烦,所以使用替代类这个方法我不觉得会有什么问题,我十分支持。对于楼主所说的"算不上是真正的分布式应用",不知道是什么意思,我并不这么认为。 回复 引用 查看
#76楼 [未注册] 2005-04-20 22:46 form
在remoting中如何传递一个form呢? 回复 引用 查看
#77楼 [未注册] 2005-05-04 17:24 阿扁
为什么要在客户端也要一个dll啊,感觉绕了一个圈.
回复 引用 查看
#78楼 [未注册] 2005-05-21 09:56 pdy
蝈蝈俊的文章我也看了,解决了不少问题。可是还有问题!
你有没有尝试过在通道中传输SqlParameter??
我找了整个网络也没找到解决方法
现在报的错是:
权限被拒绝: 无法远程调用非公共或静态方法 回复 引用 查看
#79楼 [未注册] 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();
}
}
其他的都没问题 回复 引用 查看
#80楼 [未注册] 2005-05-25 16:58 留心
//关闭监听;
tcpChannel.StopListening(null);
//注销通道;
ChannelServices.UnregisterChannel(tcpChannel)
我在服务器端"stop"按扭中写上以上代码后,但发现并不能关闭服务,客户端依然可与服务器连接,不知为什么:(
你的文章写的真不错
回复 引用 查看
#81楼 [未注册] 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
{
///
/// Class1 的摘要说明。
///
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
{
///
/// Class1 的摘要说明。
///
class ResumeSuperServer
{
///
/// 应用程序的主入口点。
///
[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
{
///
/// Class1 的摘要说明。
///
class ResumeClient
{
///
/// 应用程序的主入口点。
///
[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
回复 引用 查看
#82楼 [未注册] 2005-07-28 12:14 casual
关于“注册多个通道”,
“这个时候,我们必须用到System.Collection中的IDictionary接口:”
有更简单的方法,如下:
TcpServerChannel channel = new TcpServerChannel(string applicationname,int port);
Http通道也有相应的构造函数直接指定Name属性。
最后感谢作者,你的这几篇文章就是我的Remoting入门教程!
谢谢
回复 引用 查看
#83楼 [未注册] 2005-09-05 16:49 游客
wayfarer解答问题的诚意令人敬佩 回复 引用 查看
#84楼 2005-09-15 00:44 yicone
to city:
to p:
sqlParameter没有被串行化
to py:
好像只能自己改造了,你还是问蝈蝈吧
to form:
让你的form类继承自MarshalByRefObject, 使用没有参数的构造函数,(一定要使用的话,使用客户端激活方式, 这时客户端的代码使用Activator.CreateInstance()能够接收参数数组的那个重载, 好像是上面的a);所有客户端向服务器传递的参数和方法调用的返回值必须可序列化!
to 阿扁:
文章中已经介绍过了
“由于服务器端和客户端都要用到远程对象,通常的方式是生成两份完全相同的对象Dll,分别添加引用。不过为了代码的安全性,且降低客户端对远程对象元数据的相关性,我们有必要对这种方式进行改动。即在服务器端实现远程对象,而在客户端则删除这些实现的元数据。”
to frank:
虽然不能解决你的问题,但还是想知道你使用的是客户端激活方式吗? 回复 引用 查看
#85楼 2005-09-15 00:51 yicone
建议WayFarer大侠整理一下这里,文章加上评论太长了,我来回拖动鼠标几十次,好累啊!
幻想:要是有VS.NET中“ctrl" + "-"的功能就好了,或者把文章用#region #endregion按章节、小分类折叠起来就好了,目前好像不支持这俩个 回复 引用 查看
#86楼 [未注册] 2005-09-15 09:47 游客
lovecherry文中说:
SingleCall 类型对于每个客户端请求始终只有一个实例。下一个方法调用将由另一个服务器实例提供服务
wayfarer文中说:
当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例
我感觉两句话意思差很远,除了自己动手试验外,哪个更接近真相? 回复 引用 查看
#87楼 [未注册] 2005-09-18 13:24 feng
请教一下wayFarer大侠,一个.net Remoting支撑负载的问题。我们有个项目,有500个远程客户端通过1M 带宽ADSL和服务器端的中央数据库进行数据交换,每客户每分钟大概有一次调用。看了您的文章后,想采用.net Remoting 充当客户端和数据库的中间层,请问一下.net Reomoting能否支撑如此负载,假设中间层服务器的配置是PIV 2.8, 1024M RAM;以及采用哪种方式比较好,single call,工厂模式?最好服务器端能控制住同时能有多少个用户连接上来,对用户请求进行排队。
谢谢! 回复 引用 查看
#88楼 [未注册] 2005-10-15 22:33 奇思软件
问一个搞笑的问题,如果我在服务器端注册了singleton模式的远程对象,这意味着,所有客户端的调用都会调用同一个对象,并且它会维持状态,但有很多情况下,我在服务器端也要对这个对象进行一些操作,那么我该如何得到这个唯一对象的引址呢?
就拿典型的事件通知模型来说,我在服务器端有一个EventServer对象,我在每一个客户端放一个EventClient对象,这两个对象都是远程对象,并且EventServer为singleton模式的对象,当EventClient对象声明时会把自身的一段代码注册到EventServer的一个多点代理(事件)上,这样当有事件触发时,会调用这段代码,但有时,我想在服务器端也有这样的事件处理代码,这时最好的方式当然是我有EventServer的引址,直接进行事件注册,而由于不知道如何获取这个引址,所以目前我在服务器端也生明一个EventClient,并远程注册到本地机器上的服务器上,虽然能完成同样的功能,但总感觉不是十全十美,必竞在同一台电脑上我也通过远程的方式相互通信,不知你有什么建议?
邮件:[email protected] 回复 引用 查看
#89楼 [未注册] 2005-12-01 10:28 kawashima
你好,刚学.Net Remoting,我们现在需要在应用程序域中传递DataSet对象,请问如何序列化DataSet对象啊,期待你的回复,我现在非常急,谢谢! 回复 引用 查看
#90楼 [未注册] 2006-03-21 16:10 毁于随
@奇思软件
不知道使用Marshal的方法能不能实现你的要求呢? 回复 引用 查看
#91楼 2006-04-17 17:18 passing traveller
#92楼 [未注册] 2006-04-29 14:51 Aadon
汗...
偶是菜鸟,看的蒙蒙的..
学Remoting 前要先学什么? 回复 引用 查看
#93楼 [未注册] 2006-05-30 15:46 xietangzcn
"可以将Remoting放到IIS中以Web Service的方式进行。"-->我正写此程序,请教作者如何处理! 即通过WEB SERVICE的方式如何调用REMOTING,能否给个例子!
谢谢!
顺向作者问好! 回复 引用 查看
#94楼 2006-05-31 14:39 levinknight
用Marshal()方法就意味着是Singleton,那用SingleCall激活的对象呢,要怎么关闭呢? 回复 引用 查看
#95楼 [未注册] 2006-06-07 13:23 forair
我也在学习REMOTING中,不过刚学的
我想问一下,我在客户端想传一个自己定义的对象到服务端的时候它会弹出一个异常说:
由于安全限制,无法访问类型 System.EnterpriseServices.ServicedComponentMarshaler。
我上网查了一下也查不到关于ServicedComponentMarshaler的资料,
我的那个对象已经标明的[Serializable],并且那个对象我是做成一个组件的,我在客户端和服务端也引用的它的DLL了,请大家指教一下, 回复 引用 查看
#96楼 [未注册] 2006-06-07 21:46 forair
现在那个ServicedComponentMarshaler解决了,不过它现在又弹出一个异常说:
由于安全限制,无法访问类型Command.Customer(这个类是我自己定义的,是用来保存Customer信息的)
真的很郁闷啊,搞了很久啦,都还是没有解决啊。。。。。。不懂啊。。
我的项目:
服务端:
一个是Command(类库),我把它做成一个组件。里面有一个Customer.cs
一个是OfficeMartLib(类库),我也把它做成一个组件。里面有一个OfficeMartLib.cs这个里面定义了一些方法是在服务端运行的,其中一个是用来接收客户端发来的对象,就是那个Customer.
最后一个就是OfficeMartService(一个Window服务)
客户端:
就一个就是写了个一OfficeMartClient (一个Window应用程序)
我这个东东主要想做的就是在客户端通过远程服务OfficeMartService调用组件发送一个对象回服务端
因为正在学习服务组件很多东西都不了解,希望各位大哥指一下啊,
回复 引用 查看
#97楼 [未注册] 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呢
问了那么多,也不知道描述清楚没有 回复 引用 查看
#98楼 [未注册] 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"); 回复 引用 查看
#99楼 [未注册] 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);
也可以得到"张三"的阿??
请问各位我是哪里理解错了??谢谢大家。 回复 引用 查看
#100楼 [未注册] 2006-10-28 15:51 海边拾贝的流浪者
学习NET REMOTING 遇到这么多问题 真的郁闷哦
现在的问题是 服务端
- System.SystemException {"指定的转换无效。"} System.SystemException 回复 引用 查看
#101楼 [未注册] 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();
回复 引用 查看
#102楼 [未注册] 2006-11-18 16:51 Fox[匿名]
謝謝各位,這個問題已經解決了,主要是對Remoting的工作原理了解不是很深,不過今天還是突然想到了,呵呵 回复 引用 查看
#103楼 [未注册] 2006-12-19 18:11 kylin[匿名]
好文章!学习过后,受益非浅!谢谢啊,呵呵,继续努力,多出好文章!! 回复 引用 查看
#104楼 [未注册] 2007-01-16 16:35 小鱼
好文章,非常感谢! 回复 引用 查看
#105楼 [未注册] 2007-01-24 17:46 来宾
DataSet已经序列化了,就不用序列化了;
另外想问个问题:
既然把Person类做成dll放在客户端和服务器端,那还用remoting干什么,客户断直接调用自己的就可以了吧。 回复 引用 查看
#106楼 [未注册] 2007-01-26 14:24 天外飞仙
看了几遍,每次的收获都不同啊!! 回复 引用 查看
#107楼 [未注册] 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); 服务就启动不了了!!!
以上步骤跟楼主说的没差别呀,配置文件也放在服务安装的文件夹
服务器端读取配置文件还需要注意什么呢??
为什么一改成配置,服务(Win服务)就启动不了? 配置的问题?
回复 引用 查看
#108楼 [未注册] 2007-02-10 13:36 一凡
楼主你好:第一次访问你的博客,看了你的文章之后,感觉受益非浅,
但是有一点我不明白,我们不是可以直接用ADO.net可以访问吗!!干嘛这么麻烦呀 回复 引用 查看
#109楼 [未注册] 2007-03-05 10:12 zengchumin
在網上其他地方也看過這篇文件
樓主只是轉過來而已吧??? 回复 引用 查看
#110楼 [楼主] 2007-03-05 11:01 Bruce Zhang
@zengchumin
气煞我也,我自己的原创,居然有人诬蔑我是转载。拜托你在发表评论之前,先仔细看看。看清楚我发表此文的时间,看看大家的评论。
坦白说,我这篇文章在发表之后,可以说被转载的次数太多了,我也懒得理睬。可如今却因为看了别处的转载文字,反过来说我的原创是转载,真是让我哭笑不得啊。 回复 引用 查看
#111楼 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 中。其他信息: 返回参数具有无效的类型。”,百思不得其解,不知是哪出错了呢?还望解答,先谢过。
回复 引用 查看
#112楼 [楼主] 2007-03-22 16:02 Bruce Zhang
@eros
我没有看到客户端代码中remoteObject实例的定义。此外,你的IRemoteObject接口的定义程序集被部署到了客户端了吗? 回复 引用 查看
#113楼 [未注册] 2007-03-22 19:58 eros
是否在服务端的远程对象中提供工厂和远程对象的接口以及它们的完整定义,而在客户端的远程对象中仅仅提供工厂和远程对象的接口? 程序集在服务端和客户端都分别部署了.如果方便的话我把完整的代码发给你帮我看看,卡在这却不知何解心里真不好受. 回复 引用 查看
#114楼 [未注册][TrackBack] 2007-04-13 10:56 Sean(陈阳)
[引用提示]Sean(陈阳)引用了该文章, 地址: http://www.cnblogs.com/chy417/archive/2007/04/13/711735.html 回复 引用 查看
#115楼 [未注册][TrackBack] 2007-04-13 10:57 Sean(陈阳)
[引用提示]Sean(陈阳)引用了该文章, 地址: http://www.cnblogs.com/chy417/archive/2007/04/13/711741.html 回复 引用 查看
#116楼 [未注册][TrackBack] 2007-04-13 10:58 Sean(陈阳)
[引用提示]Sean(陈阳)引用了该文章, 地址: http://www.cnblogs.com/chy417/archive/2007/04/13/711745.html 回复 引用 查看
#117楼 [未注册][TrackBack] 2007-04-13 11:10 Sean(陈阳)
[引用提示]Sean(陈阳)引用了该文章, 地址: http://www.cnblogs.com/chy417/archive/2007/04/13/711765.html 回复 引用 查看
#118楼 2007-04-23 15:57 Nina
有沒remoting應用在web的啊。我現在看到的都是win方面的 回复 引用 查看
#119楼 [未注册] 2007-04-26 11:18 kevin
好文章,受益非浅! 回复 引用 查看
#120楼 [未注册] 2007-04-26 17:56 小杨
文章很好啊,谢谢,还有关于这样的资料吗??我的E_mail是[email protected]
回复 引用 查看
#121楼 [未注册] 2007-04-27 10:33 [email protected]
今天才开始看remoting 感觉好抽象。
要求能调用另一台电脑上的程序或进程。
请问有没好的思路啊?
或者有好的代码等就感激不尽...
代码:[email protected] 并请留言说明下 谢谢 回复 引用 查看
#122楼 2007-05-10 15:43 在北京的湖南人
一直不明白为什么需要把远程对象程序集服务器端和客户端都需要布置?还是客户端只需要类似ws那样的代理类的具体远程对象信息就可以了呀?那样的话,那我们是不是可以根据接口来编程呢? 还请指教! 回复 引用 查看
#123楼 [未注册][TrackBack] 2007-05-14 13:52 cainiao
Microsoft.NetRemoting系列专题之三:Remoting事件处理全接触 我写的.NetRemoting系列专题: Microsoft.NetRemoting系列专题...
[引用提示]cainiao引用了该文章, 地址: http://www.cnblogs.com/rd0204/archive/2007/05/14/745735.html 回复 引用 查看
#124楼 [未注册] 2007-05-14 23:44 fee
good 回复 引用 查看
#125楼 [未注册] 2007-06-11 10:34 andyli
不错,写得非常的易懂而不切深奥,学习Ing。。。 回复 引用 查看
#126楼 [未注册] 2007-07-10 16:42 一凡
请问一下水晶报表怎么调用 呀 回复 引用 查看
#127楼 [未注册][TrackBack] 2007-07-12 09:23 啊东hd
Microsoft.NetRemoting系列专题之一:.NetRemoting基础篇Microsoft.NetRemoting系列专题之二:Marshal、Disconnect与生命周期...
[引用提示]啊东hd引用了该文章, 地址: http://www.cnblogs.com/wskaihd/archive/2007/07/12/814986.html 回复 引用 查看
#128楼 [未注册][TrackBack] 2007-07-22 00:22 蛙蛙池塘
目标:输入一个关键字,从不同的资源库里检索出符合条件的资源条目。其中,资源库有本地硬盘上的数据,有远程web上的数据,其中前一种资源搜索由应用程序LocalSearcher来做,后一种资源的搜索由RemWebSearcher来做,而搜索的入口是一个网站DSearchWeb。DSearchWeb收到搜索请求后,分别起两个线程去调用LocalSearcher和RemWebSearcher,等它们两个的执行结果都回来后把结果组合到一起显示给客户,其中由于RemWebSearcher工作压力比较大,他和DSearchWeb不在一台机器上,它们之间靠Rmoting通信。实际应用中RemWebSearcher可以有多台来均衡搜索的压力,并且如果某台服务器搜索超时或者抛出异常,主入口程序不能崩溃。还有就是我们讲分布式搜索的结果拿到手后需要对结果进行一些排序或者敏感词过滤的操作。
[引用提示]蛙蛙池塘引用了该文章, 地址: http://www.cnblogs.com/onlytiancai/archive/2007/07/22/dSearch.html 回复 引用 查看
#129楼 [未注册] 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的方式,故对次问题十分关注,期待您的答复!! 回复 引用 查看
#130楼 [未注册] 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做这个?
回复 引用 查看
#131楼 [未注册] 2007-08-04 13:00 毛毛虫
而且客户端和服务器端都同时引用相同的dll文件,这样是不是太冗余啊?
在我的理解中,c#的remoting及其类似java的rmi。
而java中的rmi在客户端好像只放个类的接口就想了,真正实现只要放在服务器端。 回复 引用 查看
#132楼 [未注册] 2007-08-04 15:29 毛毛虫
知道怎么在一个vs中用多个工程了。
回复 引用 查看
#133楼 [未注册] 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();
这段代码中注册的通道没有体现出被使用阿?请大哥解释一下,这个注册通道到底有用吗?如果有用,是怎么被使用的呢?谢谢。 回复 引用 查看
#134楼 [未注册] 2007-08-21 17:44 在线代理
@ Nina
在web方面 ,估计都是用的web service了。何必这么麻烦呢 回复 引用 查看
#135楼 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 的帮助和支持中心。 回复 引用 查看
#136楼 [未注册][TrackBack] 2007-08-23 17:40 zhangh
Microsoft.NetRemoting系列专题之一:.NetRemoting基础篇 Microsoft.NetRemoting系列专题之二:Marshal、Disconnect与...
[引用提示]zhangh引用了该文章, 地址: http://www.cnblogs.com/zhangh/archive/2007/08/23/867155.html 回复 引用 查看