一步一步学Remoting之四:承载方式
在实际的应用中我们通常只会选择用windows服务和iis来承载远程对象。选择windows服务的原因是能自启动服务,服务器重启后不需要再去考虑启动service。选择iis的理由是我们能使用集成验证等一些iis的特性。
在msdn中可以找到相关文章:
可能大家会觉得这个过程将是一个复杂的过程,其实不然,下面说一下实现方法,步骤非常少。
先来建立远程对象
using System;
using System.Data;
using System.Data.SqlClient;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
public DataSet GetData()
{
SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["strconn"]);
SqlDataAdapter da=new SqlDataAdapter("select * from UBI_ProvinceMaster",conn);
DataSet ds=new DataSet();
da.Fill(ds);
return ds;
}
}
}
客户端仍然是一个控制台来进行测试:
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
DataTable dt=app.GetData().Tables[0];
foreach(DataRow dr in dt.Rows)
{
Console.WriteLine(dr["iPrMId"]+" "+dr["vPrMName"]);
}
Console.ReadLine();
服务端配置文件:
<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://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或者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的难度。
最后说一下前面的遗留问题,为什么会发生这个安全异常?
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>
然后客户端还要用程序进行调整:
若要使用配置文件设置反序列化级别,必须显式指定 <formatter> 元素的 typeFilterLevel 属性。虽然这通常是在服务器端指定的,但您还必须为注册来侦听回调的客户端上的任何信道指定这一属性,以控制其反序列化级别-msdn
添加了System.Runtime.Remoting.dll引用之后
using System;
using System.Collections;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
在程序前面加上:
BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 0;
TcpChannel channel = new TcpChannel(props,clientProvider,serverProvider);
ChannelServices.RegisterChannel(channel);
这样就可以了,注意:如果在同一个机器上面测试端口号应设为不同于服务器端设置的端口号,推荐设置为0。
.NET Remoting 自身不提供安全模型。然而,通过将远程对象驻留在 ASP.NET 中并使用 HTTP 通道进行通信,远程对象可以使用 IIS 和 ASP.NET 提供的基本安全服务。比较而言,TCP 通道和自定义的主机可执行文件能够提供更高的性能,但这种组合不提供内置的安全功能。
• 若要对客户端进行身份验证,请使用 HTTP 通道,在 ASP.NET 中驻留对象,以及在 IIS 中禁用匿名访问。
• 如果您不担心客户端身份验证问题,请使用 TCP 通道,它可以提供更高的性能。
• 如果您使用 TCP 通道,请使用 IPSec 保护客户端和服务器之间的通信通道。使用 SSL 来保护 HTTP 通道。
• 如果您需要对远程资源进行受信任的调用,请将组件驻留在 Windows 服务中,而不是驻留在控制台应用程序中。
• 始终不要向 Internet 公开远程对象。在这种情况下,请使用 Web 服务。
应该仅在 Intranet 中使用 .NET Remoting。应该使用内部方式从 Web 应用程序访问对象。即使对象驻留在 ASP.NET 中,也不要向 Internet 客户端公开它们,因为客户端必须是 .NET 客户端。
最后,让我们来看一篇msdn有关remoting安全的文章:
说到这里大家可能对remoting的一些基本知识稍微优点概念了,后续文章会继续不断强化这些概念!