Remoting技术简介
出现背景
Xml
Soap
序列化
Remoting出现的契机
l 分布式应用的需求迅速增长
进程之间通讯
局域网中计算机通讯
互联网中的通讯
各个领域:商业,娱乐,Peer-to-Peer,网格(Grid)……
l 原有的C/S,B/S模式和技术已经不能胜任
串口RS232,Socket,RPC,DCOM
什么是Remoting
l Remoting的词根——Remote
Remote Object
分布式对象
l Remoting的优势
性能
扩展性
可配置性
安全
生存周期管理
Remoting使用的技术
l XML
l SOAP简单对象传输协议
l 序列化
添加SerializableAttribute
实现ISerializable 接口
【实例代码】
主要演示如何把自定义对象序列化为二进制对象、XML对象和SOAP对象
首先看看自定义类
namespace mySerialize
{
[Serializable]
public class user
{
public DecimalList dl = new DecimalList();
private decimal Sum, Avg;
public user()
{
}
public void cacl()
{
this.Sum = 0;
foreach (decimal m in dl)
{
Sum += m;
}
this.Avg = Sum / dl.Count;
}
}
//默认情况下自定义类是不可序列化的。
//本来在user类可直接引用List<decimal>,但为了演示效果,使用自定义类DecimalList
[Serializable]
public class DecimalList : List<decimal>
{
}
}
再看看Windows Form窗体如何实现序列化:
public partial class Form1 : Form
{
private user myUser;
public Form1()
{
InitializeComponent();
}
private user BuildUser()
{
user u = new user();
for (int i = 0; i < Convert.ToInt32(this.tb_num.Text); i++)
{
u.dl.Add(i);
}
u.cacl();
return u;
}
//生成自定义类的对象实例
private void btn_Create_Click(object sender, EventArgs e)
{
myUser = BuildUser();
}
//生成二进制对象
private void btn_Bin_Click(object sender, EventArgs e)
{
FileStream fs = new FileStream("user.bin", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, myUser);
fs.Close();
}
//生成XML对象
//注意命名空间using System.Xml.Serialization的引用
private void btn_Xml_Click(object sender, EventArgs e)
{
FileStream fs = new FileStream("user.xml", FileMode.Create);
System.Xml.Serialization.XmlSerializer xs = new XmlSerializer(typeof(user));
xs.Serialize(fs, myUser);
fs.Close();
}
//生成SOAP对象
//首先要手工添加System.Runtime.Remoting
//再引用System.Runtime.Serialization.Formatters.Binary和
//System.Runtime.Serialization.Formatters.Soap的命名空间
private void btn_Soap_Click(object sender, EventArgs e)
{
FileStream fs = new FileStream("userSoap.xml", FileMode.Create);
SoapFormatter sf = new SoapFormatter();
sf.Serialize(fs, myUser);
fs.Close();
}
}
Remoting 框架图
远程对象的两个含义
操作远程对象
对象运行在远程,客户端向他发送消息。
MarshalByRefObject
传递远程对象
将远程的对象拿到本地,或者将本地对象发送过去。
对副本进行操作
[Serializable] 或ISerializable
谁来激活对象?
服务器激活(WellKnown)
Singleton
SingleCall
客户端激活
通道(Channels)
l 一个远程对象使用通道发送和接收消息
服务器选择一个通道来监听请求(request)
客户端选择通道来和服务器通讯
l Remoting提供了内置的通道
TCP通道和HTTP通道
你也可以编写自己的通道
开发Remoting三步走
1. 创建远程对象
2. 创建一个应用程序作为“宿主(host)”,以接收客户请求
3. 创建一个客户端调用远程对象
第一步:创建远程对象
继承System.MarshalByRefObject
public class HelloServer : MarshalByRefObject
{
……
}
第二步:创建宿主应用程序
l 注册通道
内置通道:TCP,HTTP
l 注册服务器激活的远程对象(WellKnown)
Singleton,SingleCall
URL
l 运行宿主程序
第三步:建立客户端程序
l 注册通道
内置通道:TCP,HTTP
l 根据URL得到对象代理
l 使用代理调用远程对象
传递参数
l 传递简单类型
int,doulbe,string,enum……
l 传递可序列化的类型
ArrayList,Hashtable,DataSet……
l 传递自定义类型
[Serializable]
【实例代码】
1. 首先看看简单的Remoting调用:
//创建远程对象
public class HelloWorld : MarshalByRefObject
{
public HelloWorld()
{
Console.WriteLine("HelloServer activated");
}
public String HelloMethod(String name)
{
Console.WriteLine("Server Hello.HelloMethod : {0}", name);
return "Hi there " + name;
}
}
创建服务器端程序
class Program
{
static int Main (string[] args)
{
TcpChannel tcpChannel = new TcpChannel(8085);
HttpChannel httpChannel = new HttpChannel(8086);
ChannelServices.RegisterChannel(tcpChannel);
ChannelServices.RegisterChannel(httpChannel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(HelloWorld), "user", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press Enter key to exit");
System.Console.ReadLine();
return 0;
}
}
创建客户端程序
static void Main (string[] args)
{
//使用TCP通道得到远程对象
TcpChannel tcpChannel = new TcpChannel();
ChannelServices.RegisterChannel(tcpChannel);
//获取远程对象
HelloWorld userTcp = (HelloWorld)Activator.GetObject(
typeof(HelloWorld), "tcp://localhost:8085/user");
if (userTcp == null)
{
System.Console.WriteLine("Could not locate TCP server");
}
//使用HTTP通道得到远程对象
HttpChannel httpChannel = new HttpChannel();
ChannelServices.RegisterChannel(httpChannel);
HelloWorld userHttp = (HelloWorld)Activator.GetObject(
typeof(HelloWorld), "tcp://localhost:8085/user");
if (userHttp == null)
{
System.Console.WriteLine("Could not locate HTTP server");
}
Console.WriteLine("Client1 TCP HelloMethod {0}",
userTcp.HelloMethod("Caveman1"));
Console.WriteLine("Client2 HTTP HelloMethod {0}",
userHttp.HelloMethod("Caveman2"));
Console.ReadLine();
}
以上都要注意对下面命名空间的引用和自定义类的引用:
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
2. 假设对user类进行扩展,利用Remoting传输自定义类,代码如下
namespace mySerialize
{
[Serializable]
public class User
{
string name = "";
bool male = true;
public User(string name, bool male)
{
this.name = name;
this.male = male;
}
public string Name
{
get { return name; }
set { name = value; }
}
public bool Male
{
get { return male; }
set { male = value; }
}
}
public class HelloWorld : MarshalByRefObject
{
public HelloWorld()
{
Console.WriteLine("HelloServer activated");
}
public String HelloMethod(String name)
{
Console.WriteLine(
"Server Hello.HelloMethod : {0}", name);
return "Hi there " + name;
}
public String HelloUserMethod(User user)
{
string title;
if (user.Male)
title = "先生";
else
title = "女士";
Console.WriteLine("Server Hello.HelloMethod : 你好,{0}{1}",user.Name, title);
return "你好," + user.Name + title;
}
}
}
服务器端代码不变,看看客户端怎么实现调用自定义类的方法:
static void Main (string[] args)
{
TcpChannel tcpChannel = new TcpChannel();
ChannelServices.RegisterChannel(tcpChannel);
HelloWorld userTcp = (HelloWorld)Activator.GetObject(
typeof(HelloWorld), "tcp://localhost:8085/user");
if (userTcp == null)
{
System.Console.WriteLine("Could not locate TCP server");
}
HttpChannel httpChannel = new HttpChannel();
ChannelServices.RegisterChannel(httpChannel);
HelloWorld userHttp = (HelloWorld)Activator.GetObject(
typeof(HelloWorld), "tcp://localhost:8085/user");
if (userHttp == null)
{
System.Console.WriteLine("Could not locate HTTP server");
}
Console.WriteLine("Client1 TCP HelloUserMethod {0}",
userTcp.HelloUserMethod(new User("张生", true)));
Console.WriteLine("Client2 HTTP HelloUserMethod {0}",
userHttp.HelloUserMethod(new User("崔莺莺", false)));
}
3. 我们看看WellKnownObjectMode的SingleCall属性和Singleton属性到底怎么不一样
首先我们还是对user类做一个修改,定义一个整型变量:
public class HelloWorld : MarshalByRefObject
{
public int callCounter = 0;
public HelloWorld()
{
Console.WriteLine("HelloServer activated");
}
public String HelloMethod(String name,out int counter)
{
counter = ++callCounter;
Console.WriteLine("Server Hello.HelloMethod : {0} Counter :{1}",
name, callCounter);
return "Hi there " + name;
}
}
客户端的输出语句变换如下
//多次调用
int counter;
Console.WriteLine("Client1 TCP HelloMethod {0} Counter {1}", userTcp.HelloMethod("Caveman1", out counter), counter);
Console.WriteLine("Client2 HTTP HelloMethod {0} Counter {1}", userHttp.HelloMethod("Caveman2", out counter), counter);
Console.WriteLine("Client2 HTTP HelloMethod {0} Counter {1}",
userHttp.HelloMethod("Caveman3", out counter), counter);
当服务器端的WellKnownObjectMode属性为SingleCall,运行后结果如下:
当服务器端的WellKnownObjectMode属性为Singleton,运行后结果如下:
4. 最后比较一下Value vs RefObject:
namespace mySerialize
{
[Serializable]
public class MySerialized
{
public MySerialized(int val)
{
a = val;
}
public void Foo()
{
Console.WriteLine("MySerialized.Foo called");
}
public int A
{
get
{
Console.WriteLine("MySerialized.A called");
return a;
}
set
{
a = value;
}
}
protected int a;
}
public class MyRemoteObj : System.MarshalByRefObject
{
public MyRemoteObj(int val)
{
a = val;
}
~MyRemoteObj()
{
Console.WriteLine("MyRemote destructor");
}
public void Foo()
{
Console.WriteLine("MyRemote.Foo called");
}
public int A
{
get
{
Console.WriteLine("MyRemote.A called");
return a;
}
set
{
a = value;
}
}
protected int a;
}
public class HelloWorld : MarshalByRefObject
{
public HelloWorld()
{
Console.WriteLine("HelloServer activated");
}
public String HelloMethod(String name)
{
Console.WriteLine(
"Server Hello.HelloMethod : {0}", name);
return "Hi there " + name;
}
public MySerialized GetMySerialized()
{
return new MySerialized(4711);
}
public MyRemoteObj GetMyRemote()
{
return new MyRemoteObj(4712);
}
}
}
客户端的输出代码如下
MySerialized ser = userTcp.GetMySerialized();
if (!RemotingServices.IsTransparentProxy(ser))
{
Console.WriteLine("ser is not a transparent proxy");
}
ser.Foo();
MyRemoteObj rem = userTcp.GetMyRemote();
if (RemotingServices.IsTransparentProxy(rem))
{
Console.WriteLine("ser is a transparent proxy");
}
rem.Foo();
[MSDN注解]
Activator.GetObject 方法 (Type, String)
为指定类型和 URL 所指示的已知对象创建一个代理。
返回值
一个代理,它指向由所请求的已知对象服务的终结点。
调用代理向远程对象发送消息。对代理调用方法之前,不向网络发送消息。
RemotingServices.IsTransparentProxy 方法
返回一个布尔值,该值指示给定的对象是透明代理还是实际对象。
客户端在跨任何类型的远程处理边界使用对象时,对对象使用的实际上是透明代理。透明代理使人以为实际对象驻留在客户端空间中。它实现这一点的方法是:使用远程处理基础结构将对其进行的调用转发给真实对象。
透明代理本身由 RealProxy 类型的托管运行时类的实例收容。RealProxy 实现从透明代理转发操作所需的部分功能。代理对象继承托管对象(例如垃圾回收、对成员和方法的支持)的关联语义,可以将其进行扩展以形成新类。这样,该代理具有双重性质,一方面,它需要充当与远程对象(透明代理)相同的类的对象;另一方面,它本身是托管对象。
可以在不考虑 AppDomain 中任何远程处理分支的情况下使用代理对象。应用程序不需要区分代理引用和对象引用。但是,处理激活、生存期管理和事务等问题的服务提供程序需要进行这种区分。
SAOs的配置文件
配置文件
1. 使用配置文件的好处
简化代码
随时更改,通道,端口,URL的设置而不用重新编译
2. .Net提供了Remoting配置文件的标准
3. XML格式的配置文件
4. 推荐在实际项目中使用配置文件
配置文件举例――服务器端
<configuration>
<system.runtime.remoting>
<application>
<service>
<!-- RemotingSamples :名称空间-->
<!--HelloServer:类名-->
<!--General:类定义所在的程序集-->
<wellknown mode="Singleton" objectUri="SayHello"
type="RemotingSamples.HelloServer, General" />
</service>
<channels>
<!---ref:引用另外一个配置文件,http在machine配置文件中配置-->
<channel port="8086" ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
配置文件举例——客户端
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown url="http://localhost:8086/SayHello"
type="RemotingSamples.HelloServer, General" />
</client >
<channels>
<channel port=“0" ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
使用配置文件的代码
配置文件可以写在任意的.config文件中。
Server.cs
Server.exe.config:配置文件名
RemotingConfiguration.Configure("Server.exe.config");//硬编码
Client.cs
RemotingConfiguration.Configure(@"client.exe.config");
HelloServer obj = new HelloServer();
使用new方法,并不是得到构造函数,实际上是得到一个HelloServer的代理,可以设置断点查看是否为代理?
使用App.config作为配置文件
l App.config在编译后名称自动变为[可执行文件的文件名].exe.config
l 如果使用App.config作为Remoting的配置文件,则代码可以这样写:
string cfg = System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;//软编码
RemotingConfiguration.Configure(cfg);
引用通道的配置
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknownurl=" http://localhost:8086/SayHello"
type="RemotingSamples.HelloServer, General" />
</client >
<channels>
<channel port=“0" ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Machine.config
位置
%WINDIR%/Microsoft.NET/Framework/[version]/CONFIG
租约
对象生存周期
客户机检测服务器是否可用
调用远程对象的方法是否出现System.Runtime.Remoting.RemotingException
服务器检测客户机是否可用
租约分布式垃圾回收器(LDGC)只对Singleton对象和客户端激活对象有效
而SingleCall每一次调用后自己回收自己
Remoting 框架图
涉及到的内容:Client-Activated 和Singleton
租约的配置
租约配置 |
默认值(秒) |
InitialLeaseTime (初始租约时间) |
300 |
RenewOnCallTime |
120 |
SponsorshipTimeout (发起者一旦租约到期的等待时间) |
120 |
LeaseManagerPollTime (租约管理者的轮询时间) |
10 |
续约
隐式续约
每次调用远程对象上的方法的时候自动进行。
显示的续约
ILease.Renew();//通过代理的方法得到ILease接口,由客户端主动控制
发起租约
ISponsor接口//实现这个接口,服务器端为发起者自动续约,由租约管理器自动管理
ILease.Register()
[实例代码]
1. 服务器端激活:
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(HelloServer),
"SayHello",
WellKnownObjectMode.Singleton);
客户端的代码段:
ILease lease = (ILease)obj.GetLifetimeService();
if (lease != null)
{
Console.WriteLine("Lease Configuration:");
Console.WriteLine("InitialLeaseTime: " + lease.InitialLeaseTime);
Console.WriteLine("RenewOnCallTime: " + lease.RenewOnCallTime);
Console.WriteLine("SponsorshipTimeout: " + lease.SponsorshipTimeout);
Console.WriteLine(lease.CurrentLeaseTime);
}
2. 客户端激活
object[] attrs = { new UrlAttribute("tcp://localhost:8085/Hello") };
HelloServer obj = (HelloServer)Activator.CreateInstance(
typeof(HelloServer), null, attrs);
服务器端代码段:
RemotingConfiguration.ApplicationName = "Hello";
RemotingConfiguration.RegisterActivatedServiceType(typeof(HelloServer));
3. 改变租约
l 写代码的方式:重写InitializeLifetimeService方式(MarshalByRefObject的方法)
public override object InitializeLifetimeService()
{
ILease lease=(ILease)base.InitializeLifetimeService();
if(lease.CurrentState==LeaseState.Initial)
{
lease.InitialLeaseTime=TimeSpan.FromSeconds(3);
lease.SponsorshipTimeout=TimeSpan.FromSeconds(10);
lease.RenewOnCallTime=TimeSpan.FromSeconds(2);
}
return lease;
}
租约永不过期
lease.InitialLeaseTime=TimeSpan.FromSeconds(0);
l 写配置文件
在服务器配置文件的application节点下添加:
<lifetime leaseTime=" 7M " sponsorshipTmeout=" 7M " renewOnCallTime=" 7M "/>
在服务器段代码改成:
RemotingConfiguration.Configure("Server.exe.config");
CAOs
Remoting 框架图
涉及到的内容:Client-Activated 以及Registered 和Created by factory
保存客户状态
客户端激活对象
l 服务器为每个客户端创建一个实例
l 在租约时间到期并且垃圾回收器发挥作用之前对象将一直处于激活状态
【实例代码】
Remoting传输对象的类定义
public class ClientActivatedType : MarshalByRefObject
{
int count = 0;
public int Increase()
{
count++;
Console.WriteLine("Increase Method was Called, Count is {0}",count);
return count;
}
}
从配置文件来看,一般来说如果是服务器激活的话,service或者client节点下的标签为wellknown;如果是客户端激活,即为activated。
服务器端配置文件
<application>
<service>
<activated type="ClientActivatedType, General"/>
</service>
<channels>
<channel port="8080" ref="http" />
</channels>
</application>
代码文件
public static void Main (string[] Args)
{
RemotingConfiguration.Configure("server.exe.config");
Console.WriteLine("The server is listening. Press Enter to exit....");
Console.ReadLine();
Console.WriteLine("Recycling memory...");
GC.Collect();
GC.WaitForPendingFinalizers();
}
客户端配置文件
<application>
<client url="http://localhost:8080">
<activated type="ClientActivatedType, General"/>
</client>
<channels>
<channel ref="http" port="0" />
</channels>
</application>
代码文件
public static void Main (string[] Args)
{
RemotingConfiguration.Configure("client.exe.config");
ClientActivatedType CAObject = new ClientActivatedType();
Console.WriteLine("Call Increase Method {0}", CAObject.Increase());
Console.WriteLine("Call Increase Method {0}", CAObject.Increase());
Console.ReadLine();
Console.WriteLine("Call Increase Method {0}", CAObject.Increase());
Console.WriteLine("Call Increase Method {0}", CAObject.Increase());
Console.WriteLine("Press Enter to end the client application domain.");
Console.ReadLine();
}
通过上面的文件,运行后我们可以看出
l 无论客户段执行多少个实例,count始终是从0开始,也就是说服务器为每个客户端创建一个实例
客户端发起租约
l 发起者(Sponsor)
发起者是可以为远程对象更新租约的对象。
l ISponsor接口
l ClientSponsor类
System.Runtime.Remoting.Lifetime
【实例代码】
public class ClientActivatedType : MarshalByRefObject
{
// Overrides the lease settings for this object.
public override Object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
// Normally, the initial lease time would be much longer.
// It is shortened here for demonstration purposes.
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromSeconds(3);
lease.SponsorshipTimeout = TimeSpan.FromSeconds(10);
lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
}
return lease;
}
public string RemoteMethod()
{
// Announces to the server that the method has been called.
Console.WriteLine("ClientActivatedType.RemoteMethod called.");
// Reports the client identity name.
return "RemoteMethod called. " + WindowsIdentity.GetCurrent().Name;
}
}
注意typeFilterLevel="Full"的含意?
typeFilterLevel默认值是low状态,更改的原因在于客户端定义的MyClientSponsor类是要发送到服务器端,必然要经过序列化和反序列化的过程,为了让他们能够自动进行反序列化的操作,所以要把两个formatter的级别设置为full,否则程序就会出错
客户端代码:
<application>
<client url="http://localhost:8080">
<activated type="ClientActivatedType, General"/>
</client>
<channels>
<channel ref="http" port="0">
<serverProviders>
<formatter ref="soap" typeFilterLevel="Full"/>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
</channel>
</channels>
</application>
public class Client
{
public static void Main (string[] Args)
{
// Loads the configuration file.
RemotingConfiguration.Configure("client.exe.config");
ClientActivatedType CAObject = new ClientActivatedType();
ILease serverLease = (ILease)RemotingServices.GetLifetimeService(CAObject);
MyClientSponsor sponsor = new MyClientSponsor();
// Note: If you do not pass an initial time, the first request will
// be taken from the LeaseTime settings specified in the
// server.exe.config file.
serverLease.Register(sponsor);
// Calls same method on each object.
Console.WriteLine("Client-activated object: " + CAObject.RemoteMethod());
Console.WriteLine("Press Enter to end the client application domain.");
Console.ReadLine();
}
}
public class MyClientSponsor : MarshalByRefObject, ISponsor
{
private DateTime lastRenewal;
public MyClientSponsor()
{
lastRenewal = DateTime.Now;
}
//必须重写的ISponsor方法
public TimeSpan Renewal(ILease lease)
{
Console.WriteLine("I've been asked to renew the lease.");
Console.WriteLine("Time since last renewal:" +
(DateTime.Now - lastRenewal).ToString());
lastRenewal = DateTime.Now;
return TimeSpan.FromSeconds(20);
}
}
服务器端代码:
<application>
<service>
<activated type="ClientActivatedType, General"/>
</service>
<channels>
<channel port="8080" ref="http">
<serverProviders>
<formatter ref="soap" typeFilterLevel="Full"/>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
</channel>
</channels>
</application>
public class Server
{
public static void Main (string[] Args)
{
// Loads the configuration file.
RemotingConfiguration.Configure("server.exe.config");
Console.WriteLine("The server is listening. Press Enter to exit....");
Console.ReadLine();
Console.WriteLine("Recycling memory...");
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
运行起来的结果如下(每个10秒发起者向服务器续约一次):