WCF 项目应用连载[3] - 双向通信 实例管理与服务端监控

时间:2024-01-05 21:55:20

WCF 项目应用连载[1] - 索引 - 轻量级的Log系统 - Lig Sample -序

第二节我们已经创建了Lig项目,并且能稳定工作了。现在我们来改进ILigAgent接口,实现WCF的双向通信。

3.1 双向通信

_________________________________________________________________________________

ILigAgent 增加订阅-发布 接口

3.1.1 关于订阅 – 发布


1) 发布-订阅设计模式又称观察者模式,它是软件设计模式中的一种,有一点要明白,它并不是WCF中所特有,正好相反,WCF中的双向通信只是这种模式中的一种应用。

2) WCF采用回调接口来进行双向通信,每一个服务接口允许存在有且只能存在一个回调接口。可以用ServiceContract中的CallbackContract属性进行显式声明指定定该服务接口的回调接口。

3) 回调接口由客户端实现。Server端通过一个静态的OperationContext对象获取客户端回调实例,我们用该实例在Server端调用回调接口,即可将消息发送给Client端。

OperationContext.Current.GetCallbackChannel<T>

如你所愿

1) 我们为ILigAgent增加了RegisterClient操作接口与Subcribe接口,Subcribe用于订阅服务端的消息。

2) 同时增加了ILigAgentCallback回调接口,用来在Server端实现消息推送。回调接口我们增加OnNotifyOnline,用来客户端注册成功后,Server将生成的带ClientID信息的消息发送给注册的客户端。

注意:服务端不同的服务实例用不同的HashCode加以区分,这样允许同名客户端的存在。

    [ServiceContract(CallbackContract = typeof(ILigAgentCallback))]
public interface ILigAgent
{
[OperationContract]
LigStatus RegisterClient(string name);
[OperationContract]
LigStatus UnregisterClient(int clientID);
[OperationContract]
LigStatus Subscribe(int clientID);
[OperationContract]
LigStatus Unsubscribe(int clientID); }
public interface ILigAgentCallback
{
[OperationContract(IsOneWay = true)]
void OnNotifyMessage(string message);
[OperationContract(IsOneWay = true)]
void OnNotifyOnline(LigArgs args);
}

3.1.2 增加 数据契约类 LigArgs

说了半天。我们还没有正式应用数据契约。现在我们增加数据契约类用来在客户端与服务端进行通信时的数据传输。
此处LigArgs可以写成LigArgs : EventArgs,这样LigArgs可以变成EventHandler的事件参数岂不更好吗?

致于为什么不这样写,我们后面写ILigger时会回答这个问题。因为那时Lig已经完善。

    [DataContract]
public class LigArgs
{
public LigArgs() { }
[DataMember]
public int ClientID { get; set; }
[DataMember]
public int HashID { get; set; }
[DataMember]
public bool ConnectStatus { get; set; }
[DataMember]
public string ClientName { get; set; }
[DataMember]
public string Message { get; set; }
}

3.2 服务实例管理 – 增加客户端实例上下文类

_________________________________________________________________________________


1) 我们在LigAgent类的内部增加一个内部类ClientContext,即客户端实例上下文类,这儿用来描述所注册客户端的信息。

2) 同时增加一个静态表dicClientsContext,用来保存注册客户端信息,其键名采用独立的HashCode。

Client信息就可以保存在Server端,Server端通过CallbackInstance给订阅客户端发送订阅消息。当客户端异常离线,Server将离线客户端信息从dicClientsContext中移除,移除的Client信息会由GC自动实现垃圾回收(当然此处最佳方案是采用弱引用(WeakReference)对ClientContext进行管理在性能上会更高效)。这样实现了Server对客户端资源的有效管理。

private static Dictionary<int, ClientContext> dicClientsContext = new Dictionary<int, ClientContext>();
#region InnerClasses
class ClientContext
{
internal ClientContext() { }
internal bool IsRegistered { get; set; }
internal bool IsSubscribed { get; set; }
internal int ClientID { get; set; }
internal int HashID { get; set; }
internal string ClientName { get; set; }
internal ILigAgentCallback CallbackInstance { get; set; }
internal OperationContext Context { get; set; }
}
#endregion

3.3 客户端 – 增加LigAgentCallback类

_________________________________________________________________________________

1) 在ConnectServer中将ChannelFactory换成DuplexChannelFactory。

2) 并注册LigAgentCallback内部的事件。我们通过LigAgentCallback内部的事件将回调消息发送给客户端。

this.callbackInstance = new LigAgentCallback();
this.SubscribedEvents();
//this.chnl = new ChannelFactory<ILigAgent>(SERVICENAME);
this.chnl = new DuplexChannelFactory<ILigAgent>(callbackInstance,SERVICENAME); private delegate void MessageHandler(string message);
private delegate void OnlineHandler(LigArgs args); #region InnerClasses
class LigAgentCallback : ILigAgentCallback
{
internal LigAgentCallback() { }
internal event MessageHandler msgReceived;
internal event OnlineHandler onlined;
public void OnNotifyMessage(string message)
{
if (this.msgReceived != null)
{
this.msgReceived(message);
}
}
public void OnNotifyOnline(LigArgs args)
{
if (this.onlined != null)
{
this.onlined(args);
}
}
internal void Release()
{ }
}
#endregion

WCF 项目应用连载[3] - 双向通信 实例管理与服务端监控

图 3.3.1 在 LigManager内部订阅 LigAgentCallback内部事件

3.4 关于服务端监控 - LigAgent

_________________________________________________________________________________

原理:LigManager封装了ILigAgent接口资源。在LigAgent内部通过注册OperationContext.Channel属性的Closed事件实现对客户端的异常与正常离线监控。

context.Context.Channel.Closed += new EventHandler(OnShowOffline)

LigAgent内部用于注册LigClient客户端的内部函数AddClient

        private LigStatus AddClient(string name,out int clientID)
{
clientID = -1;
int hash = this.GetHashCode();
if (string.IsNullOrEmpty(name)) return LigStatus.InvalidClient; if (dicClientsContext.ContainsKey(hash)) return LigStatus.ClientExisted; clientID = LigCore.ActiveID; ClientContext context = new ClientContext();
context.ClientID = clientID;
context.ClientName = name;
context.HashID = hash;
context.IsRegistered = true;
context.Context = OperationContext.Current;
context.CallbackInstance = OperationContext.Current.GetCallbackChannel<ILigAgentCallback>(); context.Context.Channel.Closed += new EventHandler(OnShowOffline); dicClientsContext.Add(hash, context);
Console.WriteLine(LigCore.LiggedTime + "Client with hashCode [" + hash.ToString() + "] is online!");
return LigStatus.Success;
}

3.5 双向通信 服务端监控 测试

_________________________________________________________________________________

测试。
启动LigServer,同时启动5个LigClient客户端。写完日志记客户端离线。观看消息变化。

1) Server已经成功的实现了对Client信息的监控。当客户端异常或正常离线,服务端对客户端实例资源进行移动操作

2) LigClient注册成功后,收到了来自LigServer的信息。。。

WCF 项目应用连载[3] - 双向通信 实例管理与服务端监控

图 3.3.2  LigServer-LigClient双向通信 服务端监控

WCF 项目应用连载[3] - 双向通信 实例管理与服务端监控

图 3.3.3  LigServer-LigClient双向通信 服务端监控

WCF 项目应用连载[3] - 双向通信 实例管理与服务端监控

图 3.3.3  LigServer-LigClient双向通信 服务端监控 LigClient收来来自LigServer的信息

好了。到这里为止。Lig系统已经差不多满足我们的要求了。但还是不太完善,Lig系统还有上升的空间。我们前面说过,要优化LigClient使用的.等你看完

WCF 项目应用连载[4] - 自定义配置 扩展ServiceHost

WCF 项目应用连载[5] - 自定义配置 扩展ChannelFactory<T>  后,

WCF 项目应用连载[6] - 升级Lig服务 - 设计ILigger

将回答这一问题。

WCF 项目应用连载[6] - 升级Lig服务 - 设计ILigger以后。Lig工程将是一个比较完美的版本。

本节的源代码下载:

_________________________________________________________________________________

Litelog -WCF 项目应用连载[3] - 双向通信 并发 实例管理与服务端监控  源代码.rar

_________________________________________________________________________________

WCF 项目应用连载[4] - 自定义配置 扩展ServiceHost

参考引文:
[1] Artech.WCF全面解析[M].2012
[2] O'Reilly.WCF编程[M].2009
[3] Adnrew Trolesen.C#与.net3.5/4高级程序设计[M].2009/2013