WCF从理论到实践(8):事件广播

上文讨论了WCF中三种消息交换模式,one-way,request/reply,duplex。前两项比较简单,无需多言,duplex相对比较复杂,上文只是实现了简单的回调,在真正应用的时候,还有许多值得注意之处,本文就结合一个实际的应用例子来谈论下duplex的具体应用和非常值得我们注意的地方。

本文的出发点

通过阅读本文,您能理解以下知识:

  1. 如何实现一个基于duplex的事件广播
  2. 解析在实现duplex事件广播中的几个问题
  3. 初步探讨一下异步

本文适合的读者

本文属于中等难度的文章,需要有WCF消息交换和windows应用程序开发相关的基础知识,有关WCF消息交换,请阅读http://www.cnblogs.com/jillzhang/archive/2008/02/17/1071521.html

如何实现一个基于duplex的事件广播

在讨论如何实现之前,先看一下本文的范例所要实现的功能是什么?本文的范例实现了一个简单的分布式任务管理系统,简单的说,它是在服务端(Server Point)执行任务(Job),并且将任务的信息呈现给客户端。它有如下特征:

  1. 通过调用服务端的Accept(),客户端能连接上服务端,并保持会话。
  2. 客户端在启动的时候,可以通过远程调用GetJobs()来获取当前服务端中全部的任务,并将这些任务在客户端窗体中用列表控件呈现出来
  3. 客户端能通过调用AddJob()向服务端添加任务,当服务端完成添加操作之后,引发添加完成的事件,并向全部的客户端广播该事件
  4. 当客户端服务端发来的添加新任务事件广播的时候,客户端将新增任务添加到列表控件加以呈现
  5. 客户端可以命令服务端执行具体某个任务,当任务在开始执行和执行结束后,服务端都会像全部客户端广播任务的执行情况,并且任务的执行和事件的广播异步执行
  6. 客户端收到广播后,便可以更新任务信息。

和以前文章不同,本文先给出最后实现的效果

如何您要了解该范例得具体设计和实现,可以下载下面的文件进行分析:
范例最终实现:/Files/jillzhang/Jillzhang.Event.rar
我这里只列出范例中项目列表

项目名称

项目描述

Jillzhang.Event.Core

该项目用于定义WCF的契约,主要包括IServer服务契约,ICallback用于回调的服务契约,Job数据契约

Jillzhang.Event.Service

服务端的具体实现,其中Server实现了一个有广播事件能力的服务契约

Jillzhang.Event.Host

服务的宿主程序,一个ConsoleApplication

Jillzhang.Event.Client

客户端实现,用于消费服务端。

Jillzhang.Event.Client2

和Jillzhang.Event.Client是一个实现,但为了验证广播,可与Jillzhang.Event.Client同时消费服务端

 

解析在实现duplex事件广播中的几个问题

1) Duplex模式对服务行为ConcurrencyMode的要求

我们知道ConcurrencyMode是控制服务并发的,默认情况下ConCurrendMode的值为Single,它设置服务运行在单线程下,当上一个请求未完成之前,服务是不接受下一个请求的。而duplex在进行回调的时候,如果回调方法没有被设置为 One-Way的交换模式,服务端是会等待客户端对回调的响应的,这可不是一件好事情,因为服务端并不能保证客户端能正常地执行回调并返回数据。更多的情况下,我们期望回调在发出后能立即返回,方法有两个:a)将回调方法设置为One-Way交换模式 b)采用多线程。经过我的测试,当回调方法被设置了one-way模式后,将ConcurrencyMode设置为Single是可以实现duplex双向通讯的。 要第二种方法也非常简单,只需要将ConcurrencyMode设置为Mutiple.此时即使回调方法不是one-way模式,也是可以完成duplex的。值得说明一下的是ConcurrencyMode还有中性的属性:ConcurrencyMode.Reentrant,说句心里话,我不喜欢这个不伦不类的家伙,他能实现在单线程下同时接受多个请求,但有利必有弊,这个家伙不能保证请求事务的完整性,使用的时候应该谨慎。

2)InstanceContextMode = InstanceContextMode.PerSession却为何能实现广播?

如果将InstanceContextMode设置为PerSession,我们知道服务端对象是针对每一个会话的,也就是说每个会话会产生一个对象实例,这样如果要实现广播,我们必须将当前服务包含的会话信息用一个列表对象记录下来,广播的时候,我们遍历会话列表,进行逐个回调。本示例中巧妙的利用了Event可包括多个委托实例的特征,一个静态的Event对象针对每个会话创建一个委托实例便可以完成上述的要求。遍历回调的方法便可以编写如下:  

        private void BroadcastEvent(CallbackEventArg e, ServerEventHanlder temp)
        
{         
            
if (OnStatusChanged != null
)
            
{
                
foreach (ServerEventHanlder handler in
 temp.GetInvocationList())
                
{
                    handler.BeginInvoke(
this, e, new AsyncCallback(EndAsync), null
);
                }

            }

        }

3)困扰了我半天的问题

上篇文章中,已经对duplex有了初步的认识,本以为本文的范例实现会很顺利呢,可有一个问题却困扰了我半天,在回调的时候非常不稳定,有时能回调4.5次,有时1,2次之后,再回调却没了响应,开始百思不得其解,因为开始几次可成功回调,为何会不稳定呢?经过好一番尝试,也没能解决,回调没有响应,肯定是客户端与服务端失去了连接,会话过期就会造成双方通讯连接的中断,经过分析,我的系统是这样的

会不会在Accept后Do()方法前的过程中会话过期了呢?后来经过验证,的确是此处的问题,解决方法是通过设置操作契约的IsTerminating来实现会话的维护,当一个操作契约的IsTerminating被设置为false的时候,该操作不会导致会话的中断,将IServer设计如下便解决了我的问题 :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Jillzhang.Event.Core
{
    [ServiceContract(SessionMode 
= SessionMode.Required, CallbackContract = typeof(ICallback))]
    
public interface IServer
    
{
        [OperationContract(IsOneWay 
= true, IsInitiating = true, IsTerminating = false)]
        
void Accept();        

        [OperationContract(IsOneWay
=true,IsInitiating=false,IsTerminating=false)]
        
void Do(string jobName);

        [OperationContract(IsOneWay 
= true, IsInitiating = false, IsTerminating = false)]
        
void AddJob(Job job);

        [OperationContract(IsOneWay
=false,IsInitiating=false,IsTerminating=false)]
        List
<Job> GetJobs();
    }

}


初步探讨一下异步

进程间通讯是一件很耗时的事情,如果同步执行会造成线程的阻塞,如果是在服务端,会降低服务的处理能力(这种说法可能有些问题,我会进一步求证,估计保留,经过查证多线程在服务端的好处在于提供对单个请求用多个线程处理的能力,从而防止完成一个请求之前,无法接受新的请求),如果是在客户端,会给用户带来不好的体验。下面就分别探讨一下如何实现服务端和客户端的异步。

在服务端,一个事件的异步可以通过delegate的BeginInvoke和EndInvoke来实现,具体方法可以参见示例项目Jillzhang.Event.Service中的Server对象的方法BroadcastEvent方法的实现

而在客户端,我们可以起多个线程,当然最方便快捷的办法就是使用BackGroundWorker后台线程来处理耗时比较长的操作了,具体实现也可以参考Jillzhang.Event.Client项目中的Form1.cs实现。

本文的参考资料

  1. http://www.cnblogs.com/wayfarer/archive/2007/03/08/667865.html
  2. http://www.cnblogs.com/caishiqi/archive/2007/10/05/914671.html
  3. http://msdn.microsoft.com/msdnmag/issues/06/10/wcfessentials/default.aspx

本文中的范例 

范例最终实现:/Files/jillzhang/Jillzhang.Event.rar
 

原来的示例代码中,采用的Binding为NetTcpBinding,有朋友问用WsDualHttpBinding的时候出现异常,也作了一个示例
/Files/jillzhang/Jillzhang.Event_WsDualHttpBinding.rar

作者:jillzhang
出处:http://jillzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2008-02-24 12:06 Robin Zhang 阅读(5613) 评论(21)  编辑 收藏 网摘 所属分类: WCF

  回复  引用  查看    
#1楼[楼主]2008-02-24 12:17 | jillzhang      
越来越发现WCF很值得学习和实践。开始觉得很深奥,可接触之后,却发现它非常容易上手,上手了之后却发现还有很多问题,又觉得它深不可测了。。。
  回复  引用  查看    
#2楼2008-02-24 13:19 | Jeffrey Zhao      
文章最后对于异步的描述有个比较大的问题。
如果使用委托的BeginInvoke/EndInvoke来异步调用的话,作为调用方的服务器端的响应能力并没有提高,因为Delegate的Begin/EndInvoke还是占用了线程池中的线程,而那个线程依旧阻塞,而且由于线程切换性能反而降低。
如果要服务器端依靠异步调用提高响应能力,这需要操作系统的支持,这才能够在等待时不阻塞任何线程,这样就把线程空出来可以处理其他任务了——因此只有特定操作的异步才能提高性能,而且调用方式是特定的——不能是Delegate的Begin/EndInvoke。
对于WCF的服务,如果要真正的靠异步调用提高调用方的服务器端性能,有两种方法。
1、在Contract中显式定义BeginXXX方法和EndXXX方法,并且在OperationContract里定义设置AsyncPattern属性为true。这种做法可以选择在服务提供方、服务调用方或双方同时使用异步调用。
2、如果只需要在调用方异步调用,那么就需要使用ClientBase的InvokeAsync方法来调用。

  回复  引用  查看    
#3楼[楼主]2008-02-24 14:52 | jillzhang      
@Jeffrey Zhao
多谢老赵提醒
WCF的异步模型,目前我还只是初步的了解,我会在后面的文章着重讨论这个问题。至于老赵的说法,我会在以后的文章中通过实例来求证。

  回复  引用  查看    
#4楼2008-02-24 22:58 |       
异步和广播为啥不直接用MSMQ?
  回复  引用  查看    
#5楼[楼主]2008-02-25 08:57 | jillzhang      
@昉
那异步和广播为何要用msmq?

  回复  引用  查看    
#6楼2008-02-25 11:15 |       
@jillzhang
呵呵,msmq的好处不少:多路广播,离线处理,事务处理...

  回复  引用  查看    
#7楼[楼主]2008-02-25 13:14 | jillzhang      
@昉
msmq在事务处理等方面有很大优势,但本文目的是想学习wcf的双向通讯,它更像教程,不像用于生产的解决方案

  回复  引用  查看    
#8楼[楼主]2008-02-26 09:03 | jillzhang      
文中忘记说明了实现本范例中还要注意的一个问题
就是如果客户端是windows应用程序,是和console应用程序不一样的,当回调契约不是one-way的时候,服务端要等待客户端的reply,而windows的ui线程却不能正常地接受请求,因为它一直处于阻塞之中。没空搭理service发过来的回调请求,所以会造成服务端长时间等待无果后,引发超时异常。
解决的办法有两个
1)将回调方法设置为IsOneWay=True
2) windows客户端采用ui线程之外的线程来接受回调请求

  回复  引用  查看    
#9楼2008-02-26 12:36 | fox23      
好文啊,我也在酝酿一篇WCF的文章。
老赵的建议也不错

  回复  引用  查看    
#10楼[楼主]2008-02-26 13:32 | jillzhang      
@fox23
欢迎,到时一定拜读

  回复  引用    
#11楼2008-03-03 14:33 | 安贝[未注册用户]
单向操作不适用于异步调用,单向调用无法保证异步调用的执行方式,IsOneWay 和异步是有区别的.
  回复  引用  查看    
#12楼2008-04-24 23:00 | 天下叁      
太复杂了点,我觉得简单的做法是在服务器端维护一个List<OperationContext> clients数组也比维护一个事件简单,广播的时候只需要foreach客户端的回调方法。比如:
foreach (OperationContext client in clients)
{
try
{
client.GetCallbackChannel().OnWriteLog(entry);
}
catch (System.Exception e)
{
Trace.WriteLine("BROAD EVENT ERROR:"+e.Message);
}
}
顺便说一下,很喜欢这个系列,讲的很容易懂。已经加入WCF技术团队,期待楼主的精彩文章。

  回复  引用    
#13楼2008-06-06 21:30 | ooxxxxxxxxx[未注册用户]
而duplex在进行回调的时候,如果回调方法没有被设置为 One-Way的交换模式,服务端是会等待客户端对回调的响应的,这可不是一件好事情,
---
我好像没在前面一篇文章的例子里看到那个MyCallback被设置为One-Way啊?(难道是返回Void,并无任何输出就是One-way?)
是我理解有误?

  回复  引用    
#14楼2008-08-30 03:31 | jacob[未注册用户]
我一直有个双向通信的问题,就是当客户端非法断开(如断电关机,网线突然拔出)这种情况服务器端如何维护?!
我试过用try,不过于事无补,我的服务器端维护的是一个list<callbackclient>来的。
请问你能想到什么好的解决方法吗?!谢谢

  回复  引用    
#15楼2008-10-08 17:30 | firststar[未注册用户]
首先,谢谢张老师的文章和源码,我学习起来WCF容易多了.
张老师,书写注释是比较好的习惯对吧, 也许是时间的问题

  回复  引用  查看    
#16楼[楼主]2008-10-09 11:17 | jillzhang      
@firststar
不好意思,一直没养成那个好习惯,:)

  回复  引用    
#17楼2008-11-16 02:28 | coeus[未注册用户]
最近才开始看WCF,看了楼主的文章和例子都很不错,赞一个先:) 但是下载了本例的实例代码,有一个地方不太明白,就是客户端的代码中有一个Proxy.cs,里面写了一个ServerClient类,我想请问这么做是为什么呢,不太明白呢
  回复  引用  查看    
#18楼2008-12-19 18:11 | 金钱豹      
@jillzhang
您好! 我正在做个发布订阅系统
对于您上面提到的会话容易中断,通过IsTerminating来实现会话的维护的问题,我感觉和sendtimeout的设置可能有关吧,默认是1分钟,您的配置是1秒.
我碰到了个类似问题,想把这个修改为1秒才能捕获客户端断线时发布通知的异常,但又怕修改后出现会话容易关闭的情况.

我碰到个比较奇怪的问题,期望能得到您的指点!
wcf回调时,如果客户端断线了,服务如果默认nettcp绑定用CommunicationException就可以捕捉,可如果绑定一修改,为<security mode="None" />或者消息安全,就捕捉不到了,不知道是不是碰到bug了?
我把sendTimeout设置为1秒就可以捕获,但我怕把客户端经常踢出订阅列表





发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1079339




相关文章:

相关链接: