ChinaDHF
学而不思则罔,思而不学则殆。
posts - 41,  comments - 211,  trackbacks - 4
声明:由于本人刚刚接触 .Net Remoting 技术,对该技术的理解尚浅,文中难免有不准确甚至错误之处,欢迎大家指正。

相对于 WebService 来说,采用 .Net Remoting 技术的客户端能够订阅服务器端事件,这个功能简直太棒了。

如果想利用该技术作一个简单而又典型的应用,信息广播程序是一个不错的选择。以下代码是一个简单的广播程序,当然,它实在太简陋了。

服务端:
Code

客户端:
Code


远程对象、委托及事件重现器(需同时部署在服务端及客户端):
Code

OK,我的信息广播程序就这样完成了。

但是,我很快就发现了问题:如果我的确想让所有订阅我的广播事件的客户端都得到我要广播的信息,这个实现应该不会有问题。但是现在我有一个消息只想通知 Mike 或 John (正如以上代码),(注:可能这时不能再称为“广播”了),我的广播程序依然将这个消息通知到了每一个客户端。

可以想到的一个方法是,让事件重现器(SayEventReappear)接收到信息后先判断一下是不是发给自己的,只有发给自己的信息才激发本地事件(代码比较容易实现,不再贴出源码)。但是,这种处理只是在客户端将信息忽略而己,服务器端是照常广播了,如果你的信息非常机密,或者带宽非常有限,这显然不是好的解决办法。

考虑到 .Net Remoting 客户端订阅事件的实现原理:事件重现器在客户端实例化,并由服务器对按引用方式对其远程调用(个人理解,未经确认,欢迎指正)。如果将事件重现器订阅服务器端事件改为向服务器端“注册”事件重现器,逻辑上应该可行,这样就可以让每个客户端注册的事件重现器携带自己的客户标识,让服务端根据标识决定是否引发特定客户端的事件。修改后的代码如下:
远程对象:
Code

客户端程序:
sh.OnSay += new SayHandler(re.Say);
改为
sh.AddEventReappear(re);

OK,服务端分别检测每一个客户端的Id,然后只引发特定客户端的事件,真正的“定向广播”实现了。

但是检查一下以上代码,会发现依然存在问题:由于事件重现器的实例存在于客户端,服务端访问的是它的代理类,因此每次对 ClientId 的检查都是一次远程调用,这无疑是一种浪费。因此将程序改为在客户端注册事件重现器时提交 ClientId 或许更合理。修改后的代码如下:
远程对象:
Code
事件重现器:
Code
客户端:
sh.AddEventReappear(re);
改为:
sh.AddEventReappear("John", re);

如此一来,不仅避免了对客户端的频繁调用,而且代码也更加简洁了。

当然,同利用“事件订阅”方式实现时当订阅事件的客户端退出时应该取消订阅一样,该实现中的客户端在退出时同样应该取消在服务端的“注册”。由于只是示例程序,以上代码中并没有进行取消注册处理。

但是问题恰恰出在这儿了,如果有的客户端没有自觉的取消注册或者意外退出/断线了,服务器端该如何处理呢?当然使用事件订阅时这个问题同样存在,但我估计 .Net Remoting 会根据生命周期原则适时“清除”已订阅事件而不再响应的客户端(同样是个人猜测,没有证实)。如果服务器端将连接失败的客户端直接取消注册,显然是对客户端的一种不负责任(如果客户端因临时资源紧张或临时断线无法及时响应时将被永久取消注册),但除此之外我没有想出合适的办法进行处理,总不能让已经不存在的客户端一直处于注册状态吧?!当然还有一种折中的办法是对客户端连接失败进行计数或计时,达到一定程度后认为客户已经不存在而进行注销。

欢迎您提出宝贵建议或解决办法。
posted on 2007-11-15 17:12 东海风 阅读(2322) 评论(12)  编辑 收藏

FeedBack:
2007-11-15 17:41 | flysto [未注册用户]
最近研究Remoting中,感谢分享
  回复  引用    
2007-11-15 19:35 | 专注.NET开源 [未注册用户]
关注Remoting
  回复  引用    
2007-11-15 19:46 | t-mac.NET      
关于离线取消事件订阅,可以在服务器端进行判断,(在引发事件处)
  回复  引用  查看    
2007-11-15 20:26 | 大石头      
我不熟悉Remoting,但是据我所知,通讯双方中的一方断开连接时,另一方会收到信息的,尽管是非正常中断,另一方也可以收到,原因就在于系统会在一个指定的时间间隔内检查网络连接情况,园子里的 牧野 兄曾经提到过如何修改那个参数。
  回复  引用  查看    
2007-11-15 21:27 | Microshaoft      
参阅
SayHandler e = null;
Delegate[] D = this.OnSay.GetInvocationList();
foreach (Delegate d in D)
{
try
{
e = (SayHandler) d;
e ("asdasd");
}
catch
{
this.OnSay -= e;
}
}
  回复  引用  查看    
2007-11-16 08:59 | jecray      
请楼主把代码打包下载 方便我们新手运行 谢谢
  回复  引用  查看    
2007-11-16 09:28 | 置身珠海,学习与奋斗      
Remoting 的调用分为三种类型, 相关的信息在 WellKnownObjectMode 里的选项中.选择适合的选项和适当的操作可以解决你的问题.


SingleCall 每个传入的消息由新的对象实例提供服务。
Singleton 每个传入的消息由同一个对象实例提供服务。

  回复  引用  查看    
2007-11-16 10:27 | 无常      
这个不能跨网段
  回复  引用  查看    
2007-11-16 13:22 | AndyHai      
关于Remoting客户端生存期的问题,我有解决方法:使用生存期租约主办方(ISponsor)

MarshalByRefObject对象有个GetLifetimeService()方法,可以获取到ILease 类型的对象(也可以通过重载InitializeLifetimeService方法来获取),用于控制此实例的生存期策略。 ILease又有个Register方法,Register方法必须提供一个ISponsor对象,按MSDN的说法,这个方法可以“在不续订租约的前提下为该租约注册主办方。 ”简单的说,我们可以给远程对象设置生存期(比如说1分钟),并注册一个ISponser对象,当生存期到了之后,会自动调用ISponser对象的Renewal方法,在这个方法中,必须返回TimeSpan,这个值即是该远程对象的生存期续订时间,如果我们需要继续保留这个远程对象,可以设置为要保留的时间数(如1分钟);如果设置为0,则表示立即放弃这个远程对象,该对象将会被分布式垃圾回收器回收。

基于上面的思想,我们可以让远程对象自身实现ISponsor,实现Renewal方法。即自己成为自己的生存期租约主办方,当生存期到后,由于系统会调用Renewal方法,我们可以在这个方法中回调客户端事件,如果回调发生异常,说明是客户端已经离线,方法返回TimeSpan.Zero即可;如果回调成功,返回1分钟的时间,续订远程对象。



public class MyClientSponsor : MarshalByRefObject, ISponsor
{
public EventHandler OnPingEvents;

public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
lease.
lease.Register(this);//为自己注册生存期租约主办方
return lease;
}

//也可以为客户端提供一个Free方法,立即释放此远程实例
public void Free()
{
ILease lease = GetLifetimeService() as ILease;

if (lease != null)
lease.Unregister(this);//调用Unregister以立即将租约状态更改为到期
}

//ISponsor的实现
public TimeSpan Renewal(ILease lease)
{
TimeSpan Ret = TimeSpan.Zero;
try
{
OnPingEvents();//这是一个客户端回调,客户端响应此事件即可,不用做什么
Ret = TimeSpan.FromMinutes(1);
}
catch
{
Ret = TimeSpan.Zero;
Free();
}
return Ret;
}
}



客户端在创建了MyClientSponsor实例后,响应OnPingEvents事件即可,如果客户端断线,远程对象即可自动到期
  回复  引用  查看    
#10楼 [楼主]
2007-11-16 14:16 | 东海风      
@Microshaoft
@AndyHai
...

首先感谢关注并提供解决方法。

综合以上两位朋友提供的解决措施,其实都是基于对客户端发出请求,捕获到异常(即无响应)后认为客户端不复存在从而将客户端释放。我也想过类似解决办法(即文中提到的“服务器端将连接失败的客户端直接取消注册”),但总感觉对客户端不够负责任,因为如果客户端出现暂时不能响应后将被永久清除,从而无法继续接收服务器端事件。但除此之外恐怕也没有合适的解决办法,只能如此了。

基于这种思路,将远程对象中的 Say 方法作如下修改即可:
public void Say(string clientId, string text)
{
string[] keys = new string[this.reDict.Count];
this.reDict.Keys.CopyTo(keys, 0);
foreach (string key in keys)
{
if (key == clientId)
{
try
{
this.reDict[key].Say(text);
}
catch
{
this.reDict.Remove(key);
}
}
}
}
  回复  引用  查看    
2007-11-16 23:25 | 专注.NET开源项目 [未注册用户]
收藏~
  回复  引用    
2007-11-17 10:56 | oxsoft.cn [未注册用户]
怎么控制处理客户雄端的连接?
  回复  引用    

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-11-17 09:10 编辑过
 
另存  打印
 
计数器:

阿里妈妈再掀疯狂采购风,网站广告位严重告急,急召天下站长


<2007年11月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

与我联系

常用链接

留言簿(2)

随笔档案(39)

收藏夹(12)

技术网站

阅读排行榜