本博后续将陆续整理这些年做的一些预研demo,及一些前沿技术的研究,与大家共研技术,共同进步。

关于发布订阅有很多种实现方式,下面主要介绍WCF中的发布订阅,主要参考书籍《Programming WCF Services》,闲话不多说进入正题。使用传统的双工回调(例子 http://www.cnblogs.com/artech/archive/2007/03/02/661969.html)实现发布订阅模式存在许多缺陷,主要问题是,它会引入发布者和订阅者之间的高度耦合。订阅者必须先知道发布者在哪里,然后才能订阅它们,任何订阅者不知道的服务都无法通知事件的订阅者,部署好的应用程序中添加新的订阅者(或者移除已经存在的订阅者)是十分困难的事情。大致相同的是发布者也只能给它知道的订阅者发送通知消息,同时发布者还需要管理订阅者列表,这些与业务服务无关,这些逻辑增加了发布者的复杂度,另外在安全方面也存在订阅者与发布者也存在耦合,而且在发布者进程宕机时,所有订阅都会丢失。

要解决上面提及的问题最常见的解决方案就是发布-订阅模式(Publish-Subscribe 【OBSERVER】),如图D-1所示。

这里将订阅者区分为临时订阅者与持久订阅者,持久订阅者可以保存到磁盘上,当事件触发时可以通知订阅者,也可以很方便的通过传递回调使用回调机制,对于持久订阅者,需要记录订阅者地址,当触发事件时,发布服务将会调用持久订阅者地址 ,然后传递事件,因持久订阅者保存了订阅者地址至数据库或磁盘,因此当发布服务宕机时提高了管理性。

以上主要介绍理论,下面进入实践阶段,首先下载ServiceModelEx(Programming WCF Services 里面书籍作者提供的简化WCF编程的动态库),  https://github.com/CaseyBurns/ServiceModelEx,我们暂时不需要服务总线所以我们引入ServiceModelEx (.NET 4.0 no service bus) ,建好测试服务端(这里为了方便测试使用GUI 应用程序作为宿主),客户端。

管理临时订阅

例子D-1使用ServiceModelEx 提供的ISubscriptionService接口管理临时订阅者

   [ServiceContract]
public interface ISubscriptionService
{
[OperationContract]
void Subscribe(string eventOperation); [OperationContract]
void Unsubscribe(string eventOperation);
}

作为通用接口它不关心回调契约,然后添加临时订阅者契约继承通用接口,并设置回调契约

    [ServiceContract(CallbackContract = typeof(IMyEvents))]
public interface IMySubscriptionService : ISubscriptionService
{
}

回调契约

    [ServiceContract]
public interface IMyEvents
{
[OperationContract(IsOneWay = true)]
void OnEvent1();
[OperationContract(IsOneWay = true)]
void OnEvent2(int number);
[OperationContract(IsOneWay = true)]
void OnEvent3(int number, string text);
}

实现临时订阅服务.

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MySubscriptionService : SubscriptionManager<IMyEvents>, IMySubscriptionService,IPersistentSubscriptionService
{ }

这里有几点需要注意:服务类型必须是会话服务(InstanceContextMode = InstanceContextMode.PerCall),会话服务才能够使用回调,另外ServiceModelEx 中的类 SubscriptionManager<T> 已经实现了通用接口所定义的添加订阅者与取消订阅接口,所以这里不需要我们再写任何代码。IPersistentSubscriptionService 作为持久订阅者接口,SubscriptionManager<T> 也实现了该接口,接下来会讲到。

配置文件配置发布订阅者服务

  <system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IService1" receiveTimeout="00:25:00"
maxBufferSize="" maxReceivedMessageSize="" transactionFlow="true">
<reliableSession inactivityTimeout="00:25:00" enabled="true" />
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service behaviorConfiguration="MyBehavior" name="Service.sub.MySubscriptionService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8022/"/>
<add baseAddress="http://localhost:8023/"/>
</baseAddresses>
</host>
<endpoint name="Sub" address="Sub" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService1"
contract="Service.sub.IMySubscriptionService" />
<endpoint name="PersistentSub" address="PersistentSub" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService1"
contract="ServiceModelEx.IPersistentSubscriptionService" />
</service>
<service behaviorConfiguration="MyBehavior" name="Service.pub.MyPublishService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8022/MyPub/"/>
<add baseAddress="http://localhost:8023/MyPub/"/>
</baseAddresses>
</host>
<endpoint name="PubMyEvents" address="PubMyEvents" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService1"
contract="Service.sub.IMyEvents" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceThrottling maxConcurrentCalls="" maxConcurrentSessions="" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

其中Service.pub.MyPublishService 服务为发布者服务配置 接下来会讲到。

这样临时订阅者就实现了,接下来看持久订阅者.持久订阅者的通用接口使用ServiceModelEx中定义的IPersistentSubscriptionService

   [ServiceContract]
public interface IPersistentSubscriptionService
{
[OperationContract(Name = "SubscribePersistent")]
[TransactionFlow(TransactionFlowOption.Allowed)]
void Subscribe(string address,string eventsContract,string eventOperation); [OperationContract(Name = "UnSubscribePersistent")]
[TransactionFlow(TransactionFlowOption.Allowed)]
void Unsubscribe(string address,string eventsContract,string eventOperation); [OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
PersistentSubscription[] GetAllSubscribers(); [OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
PersistentSubscription[] GetSubscribersToContract(string eventsContract); [OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
string[] GetSubscribersToContractEventType(string eventsContract,string eventOperation); [OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
PersistentSubscription[] GetAllSubscribersFromAddress(string address);
}

这里我添加了对[OperationContract(Name = "SubscribePersistent")] 将添加订阅方法进行重命名,以区别临时订阅接口的Subscribe方法.持久订阅不需要回调函数,接下来实现持久订阅同样简单,上面已经贴过代码,ServiceModelEx中的SubscriptionManager<T>同样已经实现了IPersistentSubscriptionService接口,这样临时订阅与持久订阅完成,接下来看发布服务。

发布服务应该支持与订阅服务一样的事件契约,这是订阅服务与发布服务唯一的连接点,使用IMyEvents 作为例子,另外ServiceModelEx提供了用于简化发布服务的帮助类PublishService<T>

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyPublishService : PublishService<IMyEvents>, IMyEvents
{
public void OnEvent1()
{
FireEvent();
} public void OnEvent2(int number)
{
FireEvent(number);
} public void OnEvent3(int number, string text)
{
FireEvent(number, text);
}
}

其中FireEvent()被用作激发所有订阅者的事件,无论是临时还是持久订阅者,帮助类PublishService<T>已经做了实现,接下来配置发布服务

      <service behaviorConfiguration="MyBehavior" name="Service.pub.MyPublishService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8022/MyPub/"/>
<add baseAddress="http://localhost:8023/MyPub/"/>
</baseAddresses>
</host>
<endpoint name="PubMyEvents" address="PubMyEvents" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService1"
contract="Service.sub.IMyEvents" />
</service>

这样发布服务完成,使用Gui应用程序作为宿主,可以使用ServiceModelEx 中ServiceHost<T> 作为发布的帮助类 。

    public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
ServiceHost<MyPublishService> hostPub = new ServiceHost<MyPublishService>();
ServiceHost<MySubscriptionService> host = new ServiceHost<MySubscriptionService>();
private void Form1_Load(object sender, EventArgs e)
{
try
{
host.EnableMetadataExchange();
host.Open(); hostPub.EnableMetadataExchange();
hostPub.Open();
}
catch (Exception ex)
{
throw;
} } private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
host.Close();
}
catch (Exception)
{
try
{
host.Abort();
}
catch (Exception)
{ }
}
try
{
hostPub.Close();
}
catch (Exception)
{
try
{
hostPub.Abort();
}
catch (Exception)
{ }
}
}
}

其中 host.EnableMetadataExchange(); 能够帮助发布元数据,不需要再到配置中进行配置,服务配置好后接下来看客户端使用,

客户端可以直接添加服务引用生成服务代理,但是一般本人喜欢使用SvcUtil工具生成代理,或者干脆直接使用通道进行服务调用,后者更为我所喜爱,因为这样代码阅读行更强,更简练。例子中偷了下懒,直接添加服务引用,然后用通道调用服务,这样剩了点复制配置或者接口的功夫,所以看到例子不要感到奇怪,全因懒造成的,废话不多说,接下来看临时订阅客户端调用

        DuplexChannelFactory<IMySubscriptionService, IMySubscriptionServiceCallback> channelFactory = null;
IMySubscriptionService proxy = null;
private void btnSub_Click(object sender, EventArgs e)
{
MyEventsCallback callBack = new MyEventsCallback();
callBack.OnResultEvent += CallBack_OnResultEvent;
InstanceContext<IMySubscriptionServiceCallback> instanceContext = new InstanceContext<IMySubscriptionServiceCallback>(callBack);
channelFactory = new DuplexChannelFactory<IMySubscriptionService, IMySubscriptionServiceCallback>(instanceContext, "Sub");
proxy = channelFactory.CreateChannel();
proxy.Subscribe(null);
}

这里使用ServiceModelEx 中提供的DuplexChannelFactory<T,C>  类型安全的双向通道类创建代理,MyEventsCallback 实现回调接口,具体实现如下:

    internal class MyEventsCallback : IMySubscriptionServiceCallback
{
SynchronizationContext sc = SynchronizationContext.Current;
public event EventHandler<EventsCallbackArgs> OnResultEvent;
public void OnEvent1()
{
sc.Post(result =>
{
EventsCallbackArgs e = new EventsCallbackArgs() { Msg = string.Concat("OnEvent1", System.Environment.NewLine) };
e.Raise(this, ref OnResultEvent);
}, null);
} public void OnEvent2(int number)
{
sc.Post(result =>
{
EventsCallbackArgs e = new EventsCallbackArgs() { Msg = string.Concat("OnEvent2:", number, System.Environment.NewLine) };
e.Raise(this, ref OnResultEvent);
}, null);
} public void OnEvent3(int number, string text)
{
sc.Post(result =>
{
EventsCallbackArgs e = new EventsCallbackArgs() { Msg = string.Concat("OnEvent3:", number, "text:", text + System.Environment.NewLine) };
e.Raise(this, ref OnResultEvent);
}, null);
}
}
    public static class EventArgExtensions
{
public static void Raise<TEventArgs>(this TEventArgs e, Object sender, ref EventHandler<TEventArgs> eventDelegate) where TEventArgs : EventArgs
{
EventHandler<TEventArgs> temp = Interlocked.CompareExchange(ref eventDelegate, null, null);
if (temp != null) temp(sender, e);
}
}

SynchronizationContext 上下文提供post方法调用gui线程更新ui,e.Raise 使用扩展以线程安全方式调用事件,客户端调用订阅者就完成了,别忘了关闭代理,接下来看客户端调用发布者

客户端调用发布服务:

  public partial class PubMessageForm : Form
{
public PubMessageForm()
{
InitializeComponent();
}
ChannelFactory<IMyEvents> channelFactory = null;
IMyEvents proxy = null;
private void btnStartPub_Click(object sender, EventArgs e)
{
channelFactory = new ChannelFactory<IMyEvents>("PubMyEvents");
proxy = channelFactory.CreateChannel();
} private void PubMessageForm_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
using (proxy as IDisposable)
{ }
channelFactory.Close();
}
catch
{
channelFactory.Abort();
}
} private void btnPub_Click(object sender, EventArgs e)
{
proxy.OnEvent1();
} private void btnPub2_Click(object sender, EventArgs e)
{
proxy.OnEvent2();
} private void btnPub3_Click(object sender, EventArgs e)
{
proxy.OnEvent3(, txtPubMessage.Text);
} private void PubMessageForm_Load(object sender, EventArgs e)
{ }
}

使用ChannelFactory<T> 通道调用发布服务

这样WCF发布订阅服务就完成了,另外如果发布服务或订阅服务不需要同步绑定,可以考虑使用msmq ,这样发布-订阅模式兼具松耦合和无连接系统的优势。

需要注意的是队列化发布-订阅服务不支持临时订阅,需要使用持久订阅,具体实现在此不多讲,另外还可以结合服务发现实现另外一种模式的发布订阅模式,具体可以参考书籍《Programming WCF Services》。

Demo下载 http://files.cnblogs.com/files/skystar/Demo.7z

书中自有黄金屋,书中自有颜如玉

WCF Publisher/Subscriber 订阅-发布模式的更多相关文章

  1. Publisher/Subscriber 订阅-发布模式

    Publisher/Subscriber 订阅-发布模式 本博后续将陆续整理这些年做的一些预研demo,及一些前沿技术的研究,与大家共研技术,共同进步. 关于发布订阅有很多种实现方式,下面主要介绍WC ...

  2. 设计模式---订阅发布模式(Subscribe/Publish)

    设计模式---订阅发布模式(Subscribe/Publish) 订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象.这个主题对象在自身状态变化时,会通知所有订阅者对象,使 ...

  3. RabbitMQ下的生产消费者模式与订阅发布模式

    所谓模式,就是在某种场景下,一类问题及其解决方案的总结归纳.生产消费者模式与订阅发布模式是使用消息中间件时常用的两种模式,用于功能解耦和分布式系统间的消息通信,以下面两种场景为例: 数据接入   假设 ...

  4. Kafka下的生产消费者模式与订阅发布模式

    原文:https://blog.csdn.net/zwgdft/article/details/54633105   在RabbitMQ下的生产消费者模式与订阅发布模式一文中,笔者以“数据接入”和“事 ...

  5. AngularJS的简单订阅发布模式例子

    控制器之间的交互方式广播 broadcast, 发射 emit 事件 类似于 js中的事件 , 可以自己定义事件 向上传递直到 document 在AngularJs中 向上传递直到 rootScop ...

  6. js设计模式之代理模式以及订阅发布模式

    为啥将两种模式放在一起呢?因为这样文章比较长啊. 写博客的目的我觉得首要目的是整理自己的知识点,进而优化个人所得知识体系.知识成为个人的知识,就在于能够用自己的话表达同一种意义. 本文是设计模式系列文 ...

  7. saltstack系列(三)——zmq订阅/发布模式

    zmq订阅发布模式 server端代码: #coding=utf-8 ''''' 服务端,发布模式 ''' import zmq from random import randrange contex ...

  8. 订阅发布模式eventEmiter

    // 订阅发布模式 class EventEmitter { constructor() { this._events = {}; } on(name, callback) { if (this._e ...

  9. ionic2踩坑之订阅发布模式的实现

    原文地址:http://www.cnblogs.com/eccainiao/p/6429536.html 转载请说明. 在ionic2中实现订阅发布模式,需要用到Events. Events下面有三个 ...

随机推荐

  1. Protel99se教程二:使用protel99se原理图绘制

    使用protel99se绘制原理图,首先要先设置一下显示网格这一项,这个可以根据个人习惯,并不是一定需要这样的,在prote99se的界面的View菜下,将visible Grid选中或取消,可以选择 ...

  2. HDU 1267 下沙的沙子有几粒?

    题解:利用卡特兰数的几何意义,题目就可以转化为一个棋盘格,可以向下走或是向右走,但是不可以逾越对角线,就可以了. #include <cstdio> #include <iostre ...

  3. PLSQL 导入表到Oracle------》从一个表空间导入到其它表空间

        在用PLSQL导入.dmp文件到Oracle时出现的问题如下: Import started on 2015/11/18 10:42:44E:\oracle\product\10.2.0\db ...

  4. java实现冒泡排序,选择排序,插入排序,快速排序(简洁版)及性能测试

    1.冒泡排序是排序里面最简单的了,但性能也最差,数量小的时候还可以,数量一多,是非常慢的. 它的时间复杂度是O(n*n),空间复杂度是O(1) 代码如下,很好理解. public void bubbl ...

  5. 谁有SMI-S Provider的一些源码,能参考一下吗

    我要用OpenPegasus根据SMI-S规范来写provider,不知道如何下手啊,求高手指点

  6. 《数字图像处理原理与实践(MATLAB版)》一书之代码Part6

    本文系<数字图像处理原理与实践(MATLAB版)>一书之代码系列的Part6,辑录该书第281至第374页之代码,供有须要读者下载研究使用.代码运行结果请參见原书配图,建议下载代码前阅读下 ...

  7. doctype(文档类型)的作用是什么?转载

    <!DOCTYPE> 声明位于文档中的最前面的位置,处于 <html> 标签之前.此标签可告知浏览器文档使用哪种 HTML 或 XHTML 规范. Document Type ...

  8. ajax 基础

    <html><head><script type="text/javascript">function showHint(str){var xm ...

  9. 【转】 利用spring的profile切换不同的环境

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  10. [置顶] 程序员必知(二):位图(bitmap)

    位图是什么? 位图就是数组,一般来说是bit型的数组,具有快速定位某个值的功能,这种思想有很广泛的应用,比如下边两题: 1 找出一个不在5TB个整数中存在的数 假设整数是32位的,总共有4GB个数,我 ...