Remoting从入门到精通教程
Remoting从入门到精通教程
一、Remoting的优缺点?
优点:
1、能让我们进行分布式开发
2、Tcp通道的Remoting速度非常快
3、虽然是远程的,但是非常接近于本地调用对象
4、可以做到保持对象的状态
5、没有应用程序限制,可以是控制台,winform,iis,windows服务承载远程对象
缺点:
1、非标准的应用因此有平台限制
2、脱离iis的话需要有自己的安全机制
二、Remoting和Web服务 的区别?
ASP.NET Web 服务基础结构通过将 SOAP 消息映射到方法调用,为 Web 服务提供了简单的 API。通过提供一种非常简单的编程模型(基于将 SOAP 消息交换映射到方法调用),它实现了此机制。ASP.NET Web 服务的客户端不需要了解用于创建它们的平台、对象模型或编程语言。而服务也不需要了解向它们发送消息的客户端。唯一的要求是:双方都要认可正在创建和使用
的 SOAP 消息的格式,该格式是由使用 WSDL 和 XML 架构 (XSD) 表示的
Web 服务合约定义来定义的。
. NET Remoting 为分布式对象提供了一个基础结构。它使用既灵活又可扩展的管线向远程进程提供 .NET 的完全对象语义。ASP.NET Web 服务基于消息传递提供非常简单的编程模型,而 .NET Remoting 提供较为复杂的功能,包括支持通过值或引用传递对象、回调,以及多对象激活和生命周期管理策略等。要使用 .NET Remoting,客户端需要了解所有这些详细信息,简而言之,需要使用
.NET 建立客户端。.NET Remoting 管线还支持
SOAP 消息,但必须注意这并没有改变其对客户端的要求。如果 Remoting 端点提供 .NET 专用的对象语义,不管是否通过 SOAP,客户端必须理解它们。
三、最简单的 Remoting的例子
1、远程对象:
建立类库项目:RemoteObject
using System;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
public int
Add(int a,int b)
{
return a+b;
}
}
}
2、服务端
建立控制台项目:RemoteServer
using System;
using System.Runtime.Remoting;
namespace RemoteServer
{
class MyServer
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("RemoteServer.exe.config");
Console.ReadLine();
}
}
}
建立配置文件:app.config
<configuration>
<system.runtime.remoting>
<application name="RemoteServer">
<service>
<wellknown type="RemoteObject.MyObject,RemoteObject" objectUri="RemoteObject.MyObject"
mode="Singleton"
/>
</service>
<channels>
<channel ref="tcp" port="9999"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
3、客户端:
建立控制台项目:RemoteClient
using System;
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
Console.WriteLine(app.Add(1,2));
Console.ReadLine();
}
}
}
建立配置文件:app.config
<configuration>
<appSettings>
<add
key="ServiceURL"
value="tcp://localhost:9999/RemoteObject.MyObject"/>
</appSettings>
</configuration>
4、测试
在最后编译的时候会发现编译报错:
1、找不到app.Add()
2、找不到RemoteObject
这是因为客户端RemoteClient没有添加RemoteObject的引用,编译器并不知道远程对象存在哪些成员所以报错,添加引用以后 vs.net会在客户端也保存一个dll,可能大家会问这样如果对远程对象的修改是不是会很麻烦?其实不麻烦,对项目编译一次vs.net会重新复制 dll。
然后直接运行客户端会出现“目标主机拒绝”的异常,也说明了通道没有打开
运行服务端再运行客户端出现“找不到程序集RemoteObject”!回头想想可以发现我们并在服务端对RemoteObject添加引用,编译的时候 通过是因为这个时候并没有用到远程对象,大家可能不理解运行服务端的时候也通过?这是因为没有这个时候还没有激活远程对象。理所当然,对服务端要添加引用
远程对象,毕竟我们的对象是要靠远程承载的。
现在再先后运行服务端程序和客户端程序,客户端程序显示3,测试成功。
服务器激活的对象是其生存期由服务器直接控制的对象。服务器应用程序域只有在客户端在对象上进行方法调用时才创建这些对象,而不会在客户端调用 new 或 Activator.GetObject 时创建这些对象;这节省了仅为创建实例而进行的一次网络往返过程。客户端请求服务器激活的类型实例时,只在客户端应用程序域中创建一个代理。然而,这也意
味着当您使用默认实现时,只允许对服务器激活的类型使用默认构造函数。若要发布其实例将使用带参数的特定构造函数创建的类型,可以使用客户端激活或者动态 地发布您的特定实例。服务器激活的对象有两种激活模式(或 WellKnownObjectMode 值):Singleton 和 SingleCall。
Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求
都将由该实例来提供服务。由于 Singleton 类型具有关联的默认生存期,即使任何时候都不会有一个以上的可用实例,客户端也不会总接收到对可远程处理的类的同一实例的引用。
软件开发网 www.mscto.com
SingleCall 远程服务器类型总是为每个客户端请求设置一个实例。下一个方法调用将改由其他实例进行服务。从设计角度看,SingleCall 类型提供的功能非常简单。这种机制不提供状态管理,如果您需要状态管理,这将是一个不利之处;如果您不需要,这种机制将非常理想。也许您只关心负载平衡和
可伸缩性而不关心状态,那么在这种情况下,这种模式将是您理想的选择,因为对于每个请求都只有一个实例。如果愿意,开发人员可以向
SingleCall 对象提供自己的状态管理,但这种状态数据不会驻留在对象中,因为每次调用新的方法时都将实例化一个新的对象标识。
首先对于服务端激活的两种模式来做一个试验,我们把远程对象做如下的修改:
using System;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
private int i=0;
public int Add(int a,int b)
{
return a+b;
}
public int Count()
{
return ++i; 软件开发网 www.mscto.com
}
}
}
对客户端做以下修改:
RemoteObject.MyObject app =
(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
Console.WriteLine(app.Count());
Console.ReadLine();
第一次打开客户端的时候显示1,第二次打开的时候显示2,类推……由此验证了Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求
都将由该实例来提供服务。
把服务器端的config修改一下: <wellknown
type="RemoteObject.MyObject,RemoteObject"
objectUri="RemoteObject.MyObject" mode="SingleCall"
/>
(这里注意大小写,大写的C)
再重新运行服务端和客户端,打开多个客户端发现始终显示1。由此验证了SingleCall 类型对于每个客户端请求都会重新创建实例。下一个方法调用将由另一个服务器实例提供服务。
下面再说一下客户端的激活模式,msdn中这么写:
客户端激活的对象是其生存期由调用应用程序域控制的对象,正如对象对于客户端是本地对象时对象的生存期由调用应用程序域控制一样。对于客户端激活,当客户
端试图创建服务器对象的实例时发生一个到服务器的往返过程,而客户端代理是使用对象引用 (ObjRef) 创建的,该对象引用是从在服务器上创建远程对象返回时获取的。每当客户端创建客户端激活的类型的实例时,该实例都将只服务于该特定客户端中的特定引用,直
到其租约到期并回收其内存为止。如果调用应用程序域创建两个远程类型的新实例,每个客户端引用都将只调用从其中返回引用的服务器应用程序域中的特定实例。
理解一下,可以归纳出
1、客户端激活的时间是在客户端请求的时候,而服务端激活远程对象的时间是在调用对象方法的时候
远程对象修改如下:
using System;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
private int i=0;
public MyObject()
{
Console.WriteLine("激活");
}
public int Add(int a,int b)
{
return a+b;
}
public int Count()
{
return ++i;
}
}
}
服务端配置文件:
<configuration>
<system.runtime.remoting>
<application name="RemoteServer">
<service>
<activated
type="RemoteObject.MyObject,RemoteObject"/>
</service>
<channels>
<channel ref="tcp"
port="9999"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
客户端程序:
using System;
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
//RemoteObject.MyObject app =
(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new
object[]{new
System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
//Console.WriteLine(app.Count());
Console.ReadLine();
}
}
}
客户端配置文件:
<configuration>
<appSettings>
<add key="ServiceURL"
value="tcp://localhost:9999/RemoteServer"/>
</appSettings>
</configuration>
(这里的uri按照服务端配置文件中application元素定义的RemoteServer来写)
运行程序可以看到,在客户端启动的时候服务端就输出了“激活”,我们再转回知名模式进行测试发现只有运行了方法才会在服务端输出“激活”。
2、客户端激活可以调用自定义的构造方法,而不像服务端激活只能使用默认的构造方法
把客户端代码修改如下:
RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),new
object[]{10},new object[]{new
System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
Console.WriteLine(app.Count());这里看到我们在CreateInstance方法的第二个参数中提供了10作为构造方法 的参数。在服务端激活模式我们不能这么做。
远程对象构造方法修改如下:
public MyObject(int k) 软件开发网 www.mscto.com
{
this.i=k;
Console.WriteLine("激活");
}毫无疑问,我们运行客户端发现输出的是11而不是1了。
3、通过上面的例子,我们运行多个客户端发现出现的永远是11,因此,客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。
总结:
1、Remoting支持两种远程对象:知名的和客户激活的。知名的远程对象使用了uri作为标识,客户程序使用这个uri来访问那些远程对象,也正式为 什么称作知名的原因。对知名的对象来说2种使用模式:SingleCall和Singleton,对于前者每次调用都会新建对象,因此对象是无状态的。对
于后者,对象只被创建一次,所有客户共享对象状态,因此对象是有状态的。另外一种客户端激活对象使用类的类型来激活,uri再后台被动态创建,并且返回给
客户程序。客户激活对象是有状态的。
2、对于Singleton对象来说需要考虑伸缩性,Singleton对象不能在多个服务器上被部署,如果要跨服务器就不能使用Singleton了。
备注:个人习惯原因,在我的例子中服务端的配置都是用config文件的,客户端的配置都是基本用程序方式的
使用配置文件的优点:无需重新编译就可以配置通道和远程对象,编写的代码量比较少
使用程序定制的优点:可以获得运行期间的信息,对程序调试有利。
这里说的复杂对象是比较复杂的类的实例,比如说我们在应用中经常使用的DataSet,我们自己的类等,通
常我们会给远程的对象传递一些自己的类,或者要求对象返回处理的结果,这个时候通常也就是需要远程对象有状态,上次我们说了几种激活模式提到说只有客户端 激活和Singleton是有状态的,而客户端激活和Singleton区别在于Singleton是共享对象的。因此我们可以选择符合自己条件的激活方 式:
状态
拥有各自实例
Singleton 有
无
SingleCall 无
有
客户端激活 有 有
在这里,我们先演示自定义类的传入传出:
先说一个概念:MBV就是按值编码,对象存储在数据流中,用于在网络另外一端创建对象 副本。MBR就是按引用编组,在客户机上创建代理,远程对象创建ObjRef实例,实例被串行化传递。
我们先来修改一下远程对象:
using System;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
private MBV _mbv;
private MBR _mbr;
public int Add(int a,int b)
{
return a+b;
}
public MBV
GetMBV()
{
return new MBV(100);
}
public MBR GetMBR()
{
return new MBR(200);
}
public void SetMBV(MBV mbv)
{
this._mbv=mbv;
}
public int
UseMBV()
{
return this._mbv.Data;
}
public void SetMBR(MBR mbr)
{
this._mbr=mbr;
}
public int UseMBR()
{
return this._mbr.Data;
}
}
[Serializable]
public class
MBV
{
private int _data;
public MBV(int data)
{
this._data=data;
}
public int Data
{
get
{
return this._data;
}
set
{
this._data=value;
}
}
}
public class MBR:MarshalByRefObject
{
private int _data;
public MBR(int data)
{
this._data=data;
}
public int Data
{
get
{
return
this._data;
}
set
{
this._data=value;
}
}
}
}
Get方法用来从服务器返 回对象,Set方法用于传递对象到服务器,Use 方法用来测试远程对象的状态是否得到了保存。
我们先来测试一下客户端激活模式:(服务器端的设置就不说了)
RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
RemoteObject.MBV mbv=app.GetMBV();
Console.WriteLine(mbv.Data);
RemoteObject.MBR mbr=app.GetMBR();
Console.WriteLine(mbr.Data);
mbv=new RemoteObject.MBV(100);
app.SetMBV(mbv);
Console.WriteLine(app.UseMBV());
//mbr=new
RemoteObject.MBR(200);
//app.SetMBR(mbr);
//Console.WriteLine(app.UseMBR());
Console.ReadLine();
依次显示:100,200,100
前面2个100,200说明我们得到了服务器端返回的对象(分别是MBV和MBR方式的),后面一个100说明我们客户端建立了一个MBV的对象传递给了 服务器,因为客户端激活模式是有状态的所以我们能使用这个对象从而输出100,最后我们注释了几行,当打开注释运行后出现异常“由于安全限制,无法访问类 型
System.Runtime.Remoting.ObjRef。”这个在【通道】一节中会讲到原因。
好了,我们再来测试一下Singleton(别忘记修改客户端配置文件中的URI哦)
RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
//RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new
object[]{new
System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
后面的语句省略,运行后同样出现100,200,100-》Singleton也是能保存状态的。
SingleCall呢?修改一下服务端的Config再来一次,在“Console.WriteLine(app.UseMBV());”出现了“未将
对象引用设置到对象的实例。”因为服务端没有能够保存远程对象的状态,当然出错。
再看一下.net内置的一些复杂对象,比如DataSet,可能传入传出DataSet和DataTable在应用中比较普遍,一些不可序列话的类我们不
能直接传递,比如DataRow等,要传递的时候可以考虑放入DataTable容器中。
远程对象修改如下:
using System;
using System.Data;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
public DataSet Method(DataSet ds)
{
DataTable dt=ds.Tables[0];
foreach(DataRow dr in dt.Rows)
{
dr["test"]=dr["test"]+"_ok"; 软件开发网 www.mscto.com
}
return ds;
}
}
}
客户端修改如下:
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
DataSet ds=new
DataSet();
DataTable dt=new
DataTable();
dt.Columns.Add(new
DataColumn("test",typeof(System.String)));
DataRow dr=dt.NewRow();
dr["test"]="data";
dt.Rows.Add(dr);
ds.Tables.Add(dt);
ds=app.Method(ds);
Console.WriteLine(ds.Tables[0].Rows[0]["test"].ToString());
Console.ReadLine();
软件开发网 www.mscto.com
运行后发现输出data_ok了。在这里不管用哪种模式来激活都会得到data_ok,因为我们并没有要求远程对象来保存状态。
总结:
所有必须跨越应用程序域的本地对象都必须按数值来传递,并且应该用 [serializable] 自定义属性作标记,否则它们必须实现 ISerializable 接口。对象作为参数传递时,框架将该对象序列化并传输到目标应用程序域,对象将在该目标应用程序域中被重新构造。无法序列化的本地对象将不能传递到其他应
用程序域中,因而也不能远程处理。通过从 MarshalByRefObject 导出对象,可以使任一对象变为远程对象。当某个客户端激活一个远程对象时,它将接收到该远程对象的代理。对该代理的所有操作都被适当地重新定向,使远程处
理基础结构能够正确截取和转发调用。尽管这种重新定向对性能有一些影响,但 JIT 编译器和执行引擎 (EE) 已经优化,可以在代理和远程对象驻留在同一个应用程序域中时,防止不必要的性能损失。如果代理和远程对象不在同一个应用程序域中,则堆栈中的所有方法调用
参数会被转换为消息并被传输到远程应用程序域,这些消息将在该远程应用程序域中被转换为原来的堆栈帧,同时该方法调用也会被调用。从方法调用中返回结果时 也使用同一过程。
软件开发网 www.mscto.com
在实际的应用中我们通常只会选择用windows服务和iis来承 载远程对象。选择windows服 务的原因是能自启动服务,服务器重 启后不需要再去考虑启动service。选择iis的理由是我们能使用集成验证等一些iis的特性。
在msdn中可以找到相关文章:
http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/BuildSucApp/BSAAsecmodsecmod29.mspx
http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpguide/html/cpconRemotingExampleHostingInIIS.asp
可能大家会觉得这个过程将是一个复杂的过程,其实不然,下面说一下实现方法,步骤非常少。
先来建立远程对象
using System;
using System.Data;
using System.Data.SqlClient;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
public DataSet GetData()
{
SqlConnection conn=new
SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["strconn"]);
软件开发网 www.mscto.com
SqlDataAdapter da=new SqlDataAdapter("select
* from UBI_ProvinceMaster",conn);
DataSet ds=new
DataSet();
da.Fill(ds);
return ds;
}
}
}
客户端仍然是一个控制台来进行测试:
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
DataTable dt=app.GetData().Tables[0];
foreach(DataRow dr in dt.Rows)
{
Console.WriteLine(dr["iPrMId"]+" "+dr["vPrMName"]);
}
Console.ReadLine();
服务端配置文件:
<configuration>
<appSettings>
<add key="strconn" value="server=(local);uid=sa;pwd=;database=UBISOFT"
/>
</appSettings>
<system.runtime.remoting>
<application name="RemoteServer">
<service>
<wellknown type="RemoteObject.MyObject,RemoteObject" objectUri="RemoteObject.MyObject"
mode="SingleCall"
/>
</service>
<channels>
<channel ref="tcp" port="9999"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
运行程序,我们得到的是一个省市的列表:
一、windows服务承载
用vs.net制作一个windows服务的过程基本不超过10个步骤,所以我们不需要害怕。
1、建立一个新的windows服务项目RemoteServer1
2、打开Service1代 码视图,找到OnStart部分,加入代码
System.Runtime.Remoting.RemotingConfiguration.Configure(AppDomain.CurrentDomain.BaseDirectory + "RemoteServer1.exe.config");
(不要遗漏AppDomain.CurrentDomain.BaseDirectory
+ )
config和控制台方式的config是一样的,我们让这个windows服务做的仅仅是从config文件读出配置信息进行配置通道。别忘记添加配置 文件。
3、切换到设计视图,右键-添加安装程序
4、切换到新生成的ProjectInstaller.cs设计视图,找到serviceProcessInstaller1对Account 属性设置为 LocalSystem,对serviceInstaller1的ServiceName属性设置为RemoteServer1(服务的名字), StartType属性设置为Automatic(系统启动的时候自动启动服务)
5、别忘记对添加RemoteObject的引用
6、建立一个新的安装项目RemoteServerSetup(我们为刚才那个服务建立一个安装项目)
7、右键-添加-项目输出-主输出-选择RemoteService1-确定
8、右键-视图-自定义操作-自定义操作上右键-添加自定义操作-打开应用程序文件夹- 选择刚才那个主输出-确定
9、重新生成这个安装项目-右键-安装
10、在服务管理器中(我的电脑-右键-管理-服务和应用程序-服务)找到RemoteServer1服务,启动服务
现在就可以打开客户端测试了!
一些FAQ:
1、启动服务的时候系统说了类似“服务什么都没有做,服务已经被停止”表示什么?
表示windows服务出错了,一般是服务的程序有问题,检查服务做了什么?在我们这个程序中仅仅添加了一行代码,一般不会出现这个错误。
2、运行客户端出现“服务器无响应”?
先检查windows服务配置文件是不是正确设置了激活方式和激活对象,客户端服务端端口号是否统一?
3、运行客户端出现“无法找到程序集”?
检查windows服务配置文件是否正确配置了激活对象的类型和uri?服务是否添加了远程对象引用?
4、远程对象类中有用到
System.Configuration.ConfigurationSettings.AppSettings["strconn"],但是远程对象
并没有配置文件,它从哪里读取这个config的?
因为远程对象不是独立存在的,它是被windows服务承载的,因此它从windows服务的配置文件中读取一些配置信息,远程对象本生不需要配置文件。
5、安装的时候是不是要卸载服务?
不需要,安装程序会 停止服务端-》卸载服务-》安装服务
6、在正式使用的时候怎么部署我们的系统?
如果客户端是程序仅仅只要把安装项目下面3个文件传到服务器进行安装,配置好config文件(比如连接字符 串),开启服务即可。如果客户端是网站,同样 把服务在服务器安装,配置好config文件(比如连接字符串),开启服务,最后把网站传到web服务器(可能和service不是同一个服务器)。
7、部署的时候需要传远程对象dll吗?
不需要,可以看到安装项目中已经自动存在了这个dll。
8、这样的系统有什么特点?
一个web服务器,多个service服务器,多个sqlservice服务器,web服务器负担比较小,所有的逻辑代码都分布到不同的service服 务器上面。
最后说一个测试的tip:
如果我们远程调用对象进行测试程序非常麻烦,我们需要这么做
修改了远程对象-》重新编译安装程序-》在自己机器重新安装服务-》启动服务-》查看结果
其实可以这么做:
1、修改远程对象中的连接数据库字 符串,由于不是远程对象了,我们必须从本地读取连接字符串,比如上列我们直接修改为:
SqlConnection conn=new
SqlConnection("server=(local);uid=sa;pwd=;database=UBISOFT");
2、修改客户端代码,直接实例化远程对象
//RemoteObject.MyObject app =
(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
RemoteObject.MyObject app = new RemoteObject.MyObject();
等到正式部署的时候我们还原数据库连接字符串从config文件中读取,还原远程对象从远程读取即可。
如果对windows服务还不是很清楚,请看以下文章:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbwlkwalkthroughcreatingwindowsserviceapplication.asp
http://www.cnblogs.com/lovecherry/archive/2005/03/25/125527.html
这里来说一下iis承载方式,顺便简单说一下remoting的通道和【复杂 对象】中的遗留问题。
首先明确一点:iis来承载的话只能是http通道方式的。
我们来建立一个web项目,比如叫remoting,删除项目中的所有webform,把远程对象dll-RemoteObject.dll复制到项目的 dll文件夹下 面,然后打开web.config进行服务端设置:<configuration>
<appSettings>
<add key="strconn" value="server=(local);uid=sa;pwd=;database=UBISOFT"
/>
</appSettings>
<system.runtime.remoting>
<application>
<service>
<wellknown type="RemoteObject.MyObject,RemoteObject" objectUri="MyObject.soap"
mode="SingleCall"
/>
</service>
<channels>
<channel ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
来分析一下这个config:
1、可能大家还不是很理解type属性,其实type属性分两部分<命 名空间.类 名>,<程序集>
2、objectURi是用来表示对象的uri的,到时候我 们用这个uri来连接到服务端
3、我们需要为uri指定soap(soap格式化)或者rem(二进制格式化)后缀
要进行测试其实很简单,我们在浏览器输入:http://localhost/remoting/MyObject.soap?wsdl
进行测试,如果发生问题基本就是配置文件的问题或者对象dll没有正确复制到dll目录
接下来修改一下客户端的配置文件就可以了,主要是修改地址。
<configuration>
<appSettings>
<add key="ServiceURL" value="http://localhost/remoting/MyObject.soap"/>
</appSettings>
</configuration>
iis承载方式默认是80端口,我们不需要在端口
上做任何设置。还需要注意到的是iis方式,我们使用这样的格式作为地址:
http://ip地址/虚拟目录/远程对象.soap
运行了客户端以后如果我们的数据量比较大的话,就算是本机我们也能感受到延迟,比tcp方式延迟厉害很多很多,其实http方式的remoting效率比
webservice还要差,具体选择http方式的remoting还是webservice还是要看我们是不是对对象的状态有需求。
iis的部署也是自动启动服务的,还有 一个优点就是可以结合iis的windows身份认证,这个参 照一些iis的配置文章,这里就不说了。
下面还是要来看一下两种【通道】:
默认情况下,HTTP 通道使用
SOAP 格式化程序,因此,如果客户端需要通过 Internet 访问对象,则可以使用 HTTP 通道。由于这种方法使用 HTTP,所以允许客户端通过防火墙远程访问 .NET 对象。将这些对象集成在 IIS 中,即可将其配置为 Web 服务对象。随后,客户端就可以读取这些对象的 WSDL 文件,以便使用 SOAP 与 Remoting 对象通信。
默认情况下,TCP 通道使用二进制格式化程序。此格式化程序以二进制格式进行数据的序列化,并使用原始套接字在网络中
传送数据。如果对象部署在受防火墙保护的封闭环境中,则此方法是理想的选择。该方法使用套接字在对象之间传递二进制数据,因此性能更好。由于它使用 TCP 通道来提供对象,因此具有在封闭环境中开销较小的优点。由于防火墙和配置问题,此方法不能在 Internet 上使用。
因此我们也需要更根据自己的需求来选择通道!看看remoting有这么多可以选择的方式:选择激活模式,选择通道,选择承载方式,如此多的选择给了我们
灵活的同时也增加了理解remoting的难度。
msdn相关章节:http://msdn.microsoft.com/library/CHS/cpguide/html/cpconChannels.asp
最后说一下前面的遗留问题,为什么会发生这个安全异常?
http://www.cnblogs.com/lovecherry/archive/2005/05/20/159335.html
msdn说:
依赖于运行时类型验证的远程处理系统必须反序列化一个远程流,然后才能开始使用它,未经授权的客户端可能会试图利用反序列化这一时机。为了免受这种攻 击,.NET 远程处理提供了两个自动反序列化级别:Low 和 Full。Low(默认值)防止反序列化攻击的方式是,在反序列化时,只处理与最基本的远程处理功能关联的类型,如自动反序列化远程处理基础结构类型、有
限的系统实现类型集和基本的自定义类型集。Full 反序列化级别支持远程处理在所有情况下支持的所有自动反序列化类型。
我们首先来修改服 务端的配置文件:
<configuration>
<system.runtime.remoting>
<application name="RemoteServer">
<service>
<wellknown type="RemoteObject.MyObject,RemoteObject" objectUri="RemoteObject.MyObject"
mode="Singleton"
/>
</service>
<channels>
<channel ref="tcp" port="9999"/>
<serverProviders>
<provider ref="wsdl" />
<formatter ref="soap" typeFilterLevel="Full" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channels>
</application>
</system.runtime.remoting>
</configuration>
当然也可以用程序进行设置:
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject.MyObject),
"RemoteObject.MyObject",
WellKnownObjectMode.Singleton);
BinaryServerFormatterSinkProvider serverProvider
= new BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider
= new BinaryClientFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props =
new Hashtable();
props["port"] = 9999;
TcpChannel channel =
new TcpChannel(props,clientProvider,serverProvider);
ChannelServices.RegisterChannel(channel);
Console.ReadLine();
客户端还要用程序进行调整:
若要使用配置文件设置反序列化级别,必须显式指定 <formatter> 元素的 typeFilterLevel 属性。虽然这通常是在服务器端 指定的,但您还必须为注册来侦听回调的客户端上的任何信道指定这一属性,以控制其反序列化级别
在程序前面加上和服务端基本相同的代码:
BinaryServerFormatterSinkProvider serverProvider
= new BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider
= new BinaryClientFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props =
new Hashtable();
props["port"] = 0;
TcpChannel channel =
new TcpChannel(props,clientProvider,serverProvider);
ChannelServices.RegisterChannel(channel); 这样就可以了,注意:如果在同一个机器上面测试端口号应设为不同于服务器端设置的端口号,推荐设置为0(远程处理系统自动选择可用端口)
.NET Remoting 自身不提供安全模型。然而,通过将远程对象驻留在 ASP.NET 中并使用 HTTP 通道进行通信,远程对象可以使用 IIS 和 ASP.NET 提供的基本安全服务。比较而言,TCP 通道和自定义的主机可执行文件能够提供更高的性能,但这种组合不提供内置的安全功能。
• 若要对客户端进行身份验证,请使用 HTTP 通道,在
ASP.NET 中驻留对象,以及在 IIS 中禁用匿名访问。
• 如果您不担心客户端身份验证问题,请使用 TCP 通道,它可以提供更高的性能。
• 如果您使用 TCP 通道,请使用 IPSec 保护客户端和服务器之间的通信通道。使用 SSL 来保护 HTTP 通道。
• 如果您需要对远程资源进行受信任的调 用,请将组件驻留在 Windows 服务中,而不是驻留在控制台应用程序中。
• 始终不要向
Internet 公开远程对象。在这种情况下,请使用 Web 服务。
应该仅在 Intranet 中使用 .NET Remoting。应该使用内部方式从 Web 应用程序访问对象。即使对象驻留在 ASP.NET 中,也不要向 Internet 客户端公开它们,因为客户端必须是 .NET 客户端。
最后,让我们来看一篇msdn有关remoting安全的文章:
http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/BuildSucApp/BSAAsecmod11.mspx
说到这里大家可能对remoting的一些基本知识稍微优点概念了,后续文章会继续不断强化这些概念!
如果你还不知道什么是异步也不要紧,我们还 是来看实例,通过实例来理解才是最深刻的。
在Remoting中,我们可以使用以下几种异步的方式:
1、普通异步
2、回调异步
3、单向异步
一个一个来说,首先我们这么修改我们的远程对象:
public int ALongTimeMethod(int a,int b,int time)
{
Console.WriteLine("异步方法开始");
System.Threading.Thread.Sleep(time);
Console.WriteLine("异步方法结束");
return a+b;
}
这个方法传入2个参数,返回2个参数和表示方法执行成功,方法需要time毫秒的执行时间,这是一个长时间
的方法。
如果方法我们通过异步远程调用,这里需要注意到这个方法输出的行是在服务器端 输出的而不是客户端。因此,为了测试简单,我们还是在采用本地对象,在实现异步前我们先来看看同步的调用方法,为什么说这是一种阻塞?因为我们调用了方法
主线程就在等待了,看看测试:
DateTime
dt=DateTime.Now;
RemoteObject.MyObject app=new RemoteObject.MyObject();
Console.WriteLine(app.ALongTimeMethod(1,2,1000));
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
假设method方法是主线程的方法,需要3秒的时间:
private static void Method()
{
Console.WriteLine("主线程方法开始"); 软件开发网 www.mscto.com
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
好了,现在开始运行程序:
用了4秒,说明在我们的方法开始以后本地就一直在等待了,总共用去的时间=本地方法+远程方法,对于长时间方法调用这显然不科学!我们需要改进:
1、普通异步:
首先在main方法前面加上委托,签名和返回类型和异步方法一致。
private delegate int MyDelegate(int a,int b,int time);
main方法里面这么写:
DateTime
dt=DateTime.Now;
RemoteObject.MyObject app=new RemoteObject.MyObject();
MyDelegate md=new MyDelegate(app.ALongTimeMethod);
IAsyncResult Iar=md.BeginInvoke(1,2,1000,null,null);
Method();
if(!Iar.IsCompleted)
{
Iar.AsyncWaitHandle.WaitOne();
}
else
{
Console.WriteLine("结果是"+md.EndInvoke(Iar));
}
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
来看一下执行结果:
现在总共执行时间接近于主线程的执行时间了,等于是调用方法基本不占用时间。
分析一下代码:Iar.AsyncWaitHandle.WaitOne(); 是阻塞等待异步方法完成,在这里这段代码是不会被执行的,
因为主方法完成的时候,异步方法早就IsCompleted了,如果我们修改一下代码:IAsyncResult Iar=md.BeginInvoke(1,2,5000,null,null);
可以看到,主线程方法结束后就在等待异步方法完成了,总共用去了接近于异步方法的时间:5秒。
在实际的运用中,主线程往往需要得到异步方法的结果,也就是接近于上述的情况,我们在主线程做了少量时间的工作以后最终要是要WaitOne去等待异步操 作返回的结果,才能继续主线程操作。看第二个图可以发现,异步操作仅仅用了1秒,但是要等待3秒的主线程方法完成后再返回结果,这还是不科学啊。因此,我
们要使用委托回调的异步技术。
2、回调异步:
class MyClient
{
private delegate int MyDelegate(int a,int b,int time);
private static MyDelegate md;
[STAThread]
static void Main(string[] args)
{
DateTime dt=DateTime.Now;
//RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
RemoteObject.MyObject app=new RemoteObject.MyObject();
md=new MyDelegate(app.ALongTimeMethod);
AsyncCallback ac=new
AsyncCallback(MyClient.CallBack);
IAsyncResult Iar=md.BeginInvoke(1,2,1000,ac,null);
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒"); 软件开发网 www.mscto.com
Console.ReadLine();
}
public
static void CallBack(IAsyncResult Iar)
{
if(Iar.IsCompleted)
{
Console.WriteLine("结果是"+md.EndInvoke(Iar));
}
}
private
static void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
}可以看到我上面的注释行,去掉远程调用的注释,对下面的本地调用
注释,编译后启动服务端,再启动客户端就是远程调用了。
异步调用结束,立即就能显示出结果,如果开启远程方法的话,可以看的更加清晰:
客户端:主线程方法开始-》服务端:异步方法开始-》服务端:异步方法结束-》客户端:结果是3-》客户端:主线程方法结束-》客户端:用了 3.03125秒。
3、单向异步就是像同步调用方法那样调用方法,方法却是异步完成的,但是不能获得方法的返回值而且不能像同步方法那样取得所调用方法的异常信息!对于不需
要返回信息的长时间方法,我们可以放手让它去干就行了:
远程对象:
using System;
using System.Runtime.Remoting.Messaging;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
[OneWay]
public void ALongTimeMethodOneWay(int time)
{
Console.WriteLine("异步方法开始");
System.Threading.Thread.Sleep(time);
Console.WriteLine("异步方法结束");
}
}
} [OneWay]属性是Remoting.Messaging的一部分,别忘记using,下面看看客户端代码:
using System;
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
DateTime dt=DateTime.Now;
RemoteObject.MyObject
app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
//RemoteObject.MyObject
app=new RemoteObject.MyObject();
app.ALongTimeMethodOneWay(1000);
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
}
private static
void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
}
}
这次我们仅仅只能在远程调试,我们先让异步方法去做,然后就放心的做主线程的事情,其他不管了。
运行结果我描述一下:
软件开发网 www.mscto.com
客户端:主线程方法开始-》服务端:异步方法开始-》服务端:异步方法结束-》客户端:主线程方法结束-》客户端:用了3.8秒。
上面说的三种方法,只是异步编程的一部分,具体怎么异步调用远程方法要结合实际的例子,看是否需要用到方法的返回和主线程方法的运行时间与远程方法运行时
间等结合起来考虑,比如上述的WaitHandle也可以用轮询来实现:
while(Iar.IsCompleted==false) System.Threading.Thread.Sleep(10);总的来说远程对象的异步操作和本地对象的异步操作是非常接近。
还可以参考msdn相关文章:
http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpguide/html/cpconasynchronousprogrammingdesignpattern2.asp