Publisher/Subscriber 订阅-发布模式

本博后续将陆续整理这些年做的一些预研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="2147483647" maxReceivedMessageSize="2147483647" 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="1000" maxConcurrentSessions="10000" />
<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(2);
} private void btnPub3_Click(object sender, EventArgs e)
{
proxy.OnEvent3(3, 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

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

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

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

    本博后续将陆续整理这些年做的一些预研demo,及一些前沿技术的研究,与大家共研技术,共同进步. 关于发布订阅有很多种实现方式,下面主要介绍WCF中的发布订阅,主要参考书籍<Programming ...

  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. axis1客户端调用webservice的通用代码

    1.axis1 作为web service 客户端时,调用web service 服务端的通用代码 String url = "http://www.webxml.com.cn/webser ...

  2. openStack deep dive,Retake Policy

    Method of Payment: visa MasterCard American Express Discover

  3. Highlight On Mouseover Effect With JQuery

    How to get the xpath by clicking an html element How to get the xpath by clicking an html element Qu ...

  4. Mysql文件太大导入失败解决办法总结

    Mysql文件太大导入失败解决办法总结 在使用phpmyadmin导入数据库的时候可能会碰到由于数据库文件太大而无法导入的问题! 英文提示如下:File exceeds the maximum all ...

  5. 使用资源监控工具 glances

    http://www.ibm.com/developerworks/cn/linux/1304_caoyq_glances/ glances 可以为 Unix 和 Linux 性能专家提供监视和分析性 ...

  6. UVA-Matrix Chain Multiplication(栈)

     Matrix Chain Multiplication  Suppose you have to evaluate an expression like A*B*C*D*E where A,B,C, ...

  7. 关于javascript的沙箱模式以及缓存方法

    在javascript函数代码中,经常会不经意出现全局变量,很可能造成对全局对象的污染,由于这种弊端的存在,那么沙箱模式油然而生.沙箱模式又称为沙盒模式.隔离模式.在js中只有函数可以限定变量作用域, ...

  8. 上架app被拒原因总结

    1. Terms and conditions(法律与条款) 1.1 As a developer of applications for the App Store you are bound by ...

  9. Anatomy of a Program in Memory

    Memory management is the heart of operating systems; it is crucial for both programming and system a ...

  10. 国内外主流BI厂商对比

    BI(Business Intelligence),即商业智能或者商务智能,它是一套完整的解决方案,用来将企业中现有的数据进行有效的整合,快速准确的提供报表并提出决策依据,帮助企业做出明智的业务经营决 ...