事件通知服务用于解决多个应用程序之间的事件发布与预定的问题。在.NET平台上,跨应用程序的事件发布/预定通常以Remoting作为底层的通信基础,在此基础之上,事件通知服务使用中介者模式来简化跨应用程序的事件通知问题。
本文采用的解决方案中,有两个重要组件:事件服务器EventServer和事件客户端EventClient。EventServer作为中介者,并作为一个独立的系统,通常可以将其作为windows服务运行。EventServer和EventClient之间的关系如下所示:
每个需要事件通知的应用程序中,都包含了EventClient组件,应用程序通过EventClient与事件服务器进行交互,而当有事件发生时,EventClient也会触发相应的事件来通知应用程序。
EventServer和EventClient实现了共同的接口IEventNotification:
public
interface
IEventNotification
{
void
SubscribeEvent(
string
eventName ,EventProcessHandler handler) ;
//
预定事件
void
UnSubscribeEvent(
string
eventName ,EventProcessHandler handler) ;
//
取消预定
void
RaiseEvent(
string
eventName ,
object
eventContent) ;
//
发布事件
}
public
delegate
void
EventProcessHandler(
string
eventName ,
object
eventContent) ;
注意,IEventNotification接口中的每个方法的第一个参数是事件名,事件名唯一标志了每个事件,它相当于一个主键。
EventClient与包含它的应用程序之间的交互通过本地事件预定/发布来完成,而与EventServer之间的交互则通过remoting完成。其实现如下:
public
class
EventClient :MarshalByRefObject ,IEventNotification
{
private
IEventNotification eventServer
=
null
;
private
Hashtable htableSubscribed
=
new
Hashtable() ;
//
eventName -- Delegate(是一个链表)
public
EventClient(
string
eventServerUri)
{
TcpChannel theChannel
=
new
TcpChannel(
0
) ;
ChannelServices.RegisterChannel(theChannel) ;
this
.eventServer
=
(IEventNotification)Activator.GetObject(
typeof
(IEventNotification) ,eventServerUri);
}
public override object InitializeLifetimeService()
{
//Remoting对象 无限生存期
return null;
}
#region
IEventNotification 成员
//
handler是本地委托
public
void
SubscribeEvent(
string
eventName, EventProcessHandler handler)
{
lock
(
this
)
{
Delegate handlerList
=
(Delegate)
this
.htableSubscribed[eventName] ;
if
(handlerList
==
null
)
{
this
.htableSubscribed.Add(eventName ,handler) ;
this
.eventServer.SubscribeEvent(eventName ,
new
EventProcessHandler(
this
.OnRemoteEventHappen)) ;
return
;
}
handlerList
=
Delegate.Combine(handlerList ,handler) ;
this
.htableSubscribed[eventName]
=
handlerList ;
}
}
public
void
UnSubscribeEvent(
string
eventName, EventProcessHandler handler)
{
lock
(
this
)
{
Delegate handlerList
=
(Delegate)
this
.htableSubscribed[eventName] ;
if
(handlerList
!=
null
)
{
handlerList
=
Delegate.Remove(handlerList ,handler) ;
this
.htableSubscribed[eventName]
=
handlerList ;
}
}
}
public
void
RaiseEvent(
string
eventName,
object
eventContent)
{
this
.eventServer.RaiseEvent(eventName ,eventContent) ;
}
#endregion
#region
OnRemoteEventHappen
///
<summary>
///
当EventServer上有事件触发时,EventServer会转换为客户端,而EventClient变成远程对象,
///
该方法会被远程调用。所以必须为public
///
</summary>
public
void
OnRemoteEventHappen(
string
eventName,
object
eventContent)
{
lock
(
this
)
{
Delegate handlerList
=
(Delegate)
this
.htableSubscribed[eventName] ;
if
(handlerList
==
null
)
{
return
;
}
object
[] args
=
{eventName ,eventContent} ;
foreach
(Delegate dg
in
handlerList.GetInvocationList())
{
try
{
dg.DynamicInvoke(args) ;
}
catch
(Exception ee)
{
ee
=
ee ;
}
}
}
}
#endregion
}
需要注意的是,EventClient从MarshalByRefObject继承,这是因为,当EventServer上有事件被触发时,也会通过Remoting Event来通知EventClient,这个时候,EventClient就是一个remoting object。另外,OnRemoteEventHappen方法必须为public,因为这个方法将会被EventServer远程调用。
下面给出EventServer的实现:
public
class
EventServer :MarshalByRefObject ,IEventNotification
{
//
htableSubscribed内部每项的Delegate链表中每一个委托都是透明代理
private
Hashtable htableSubscribed
=
new
Hashtable() ;
//
eventName -- Delegate(是一个链表)
public
EventServer()
{
}
public override object InitializeLifetimeService()
{
//Remoting对象 无限生存期
return null;
}
#region
IEventNotification 成员
//
handler是一个透明代理,指向EventClient.OnRemoteEventHappen委托
public
void
SubscribeEvent(
string
eventName, EventProcessHandler handler)
{
lock
(
this
)
{
Delegate handlerList
=
(Delegate)
this
.htableSubscribed[eventName] ;
if
(handlerList
==
null
)
{
this
.htableSubscribed.Add(eventName ,handler) ;
return
;
}
handlerList
=
Delegate.Combine(handlerList ,handler) ;
this
.htableSubscribed[eventName]
=
handlerList ;
}
}
public
void
UnSubscribeEvent(
string
eventName, EventProcessHandler handler)
{
lock
(
this
)
{
Delegate handlerList
=
(Delegate)
this
.htableSubscribed[eventName] ;
if
(handlerList
!=
null
)
{
handlerList
=
Delegate.Remove(handlerList ,handler) ;
this
.htableSubscribed[eventName]
=
handlerList ;
}
}
}
public
void
RaiseEvent(
string
eventName,
object
eventContent)
{
lock
(
this
)
{
Delegate handlerList
=
(Delegate)
this
.htableSubscribed[eventName] ;
if
(handlerList
==
null
)
{
return
;
}
object
[] args
=
{eventName ,eventContent} ;
IEnumerator enumerator
=
handlerList.GetInvocationList().GetEnumerator() ;
while
(enumerator.MoveNext())
{
Delegate handler
=
(Delegate)enumerator.Current ;
try
{
handler.DynamicInvoke(args) ;
}
catch
(Exception ee)
//
也可重试
{
ee
=
ee ;
handlerList
=
Delegate.Remove(handlerList ,handler) ;
this
.htableSubscribed[eventName]
=
handlerList ;
}
}
}
}
#endregion
}
EventServer的实现是很容易理解的,需要注意的是RaiseEvent方法,该方法在while循环中对每个循环加入了try...catch,这是为了保证,当一个应用程序无法接收通知或接收通知失败时不会影响到其它的服务器。
关于事件通知服务,可以总结为以下几点:
(1)事件通知服务采用了中介者模式,所有的EventClient只与EventServer(中介者)交互,从EventServer处预定名为eventName的事件,或发布名为eventName的事件。
(2)各个客户应用程序是对等的,它们都可以预定事件和发布事件。
(3)EventServer不会自主地触发事件,它就像一个公共区(缓存预定者)或转发器(广播事件)。
(4)EventServer 将在事件服务器上作为远程对象发布
(5)客户应用程序将通过EventClient来预定事件、发布事件。
最后,需要提出的是关于事件服务器的配置,需要将remoting的权限级别设置为FULL,否则,就会出现事件句柄无法序列化的异常。在我的示例中,EventServer的配置文件如下:
<
configuration
>
<
system
.runtime.remoting
>
<
application
>
<
service
>
<
wellknown
mode
="Singleton"
type
="EnterpriseServerBase.XFramework.EventNotification.EventServer,EnterpriseServerBase"
objectUri
="SerInfoRemote"
/>
</
service
>
<
channels
>
<
channel
ref
="tcp"
port
="8888"
>
<
serverProviders
>
<
provider
ref
="wsdl"
/>
<
formatter
ref
="soap"
typeFilterLevel
="Full"
/>
<
formatter
ref
="binary"
typeFilterLevel
="Full"
/>
</
serverProviders
>
<
clientProviders
>
<
formatter
ref
="binary"
/>
</
clientProviders
>
</
channel
>
</
channels
>
</
application
>
</
system.runtime.remoting
>
</
configuration
>
请特别注意,标志为红色的两句。 并且,在服务端程序启动时,配置Remoting:
RemotingConfiguration.Configure("EventClient.exe.config");
由于在服务端回调Client时,Client相对变成"Server",所以,Client也必须注册一个remoting通道。
<
system
.runtime.remoting
>
<
application
>
<
channels
>
<
channel
ref
="tcp"
port
="0"
>
<
clientProviders
>
<
formatter
ref
="binary"
/>
</
clientProviders
>
<
serverProviders
>
<
formatter
ref
="binary"
typeFilterLevel
="Full"
/>
</
serverProviders
>
</
channel
>
</
channels
>
</
application
>
</
system.runtime.remoting
>
并且,在客户端程序启动时,配置Remoting:
RemotingConfiguration.Configure("EventClient.exe.config");
企业开发基础设施 主目录