基于WCF回调(WCF Callback)的GPS报警推送
报警推送数据在很多软件中都有需求,比如任务提醒、消息广播、实时的监控报警等等。凡是对实时性要求越高的场景,越是需要服务器及时、准确地向客户端推送数据。一般的推送,我们可以选择使用socket,因为socket是双工通信的最佳模式。但是直接使用socket来开发,对于复杂的报警逻辑、权限判断、报警注册、数据库调用和更新处理来说,使用Socket处理,代码比较难以维护。
考虑到目前的基于部标808的GPS平台(需要完整平台源码的可以联系我购买),我们决定使用WCF来作为平台的基础服务架构,而WCF的回调模式可以满足GPS报警复杂的业务模式:
1.注册
用户注册后,需要加载自己分配的功能权限和数据权限,功能权限决定了是否能看报警。
数据权限,决定了能看到那些报警,那些车辆的报警。
2.GPS报警发布
通常我们将808GPS服务器作为报警发布者,当接收到车辆GPS终端发送上来的报警后,发布给报警服务模块,由报警服务模块再根据逻辑转发给订阅者.
3.报警订阅
部标808协议规定了32路的报警再加上其他扩展的平台报警,可多达几十种报警,客户端需要通过订阅功能来接收自己感兴趣的报警。
4.报警过滤
报警最大的问题,不是如何实时的推送到客户端,而是如何避免误报。需要有一套算法设定来过滤掉无效的报警。频繁的误报,会对客户造成困扰,也会造成狼来了的效果,多次误报后,用户就失去了对报警的信任。如在工厂围墙的红外监控报警,报警设定的过于敏感,一有风吹草动就报警,保安就不得休息,时间长了就不看它,当有人非法翻越围墙的时候,反而没有看到。简单的过滤,就是时间过滤法,如当报警超过10秒后推送到客户端。
5.报警显示与处理
报警如何显示,如何避免重复显示,累积的未处理报警如何处理等等,这个也是个比较麻烦的用户体验的问题,很少有人去问问用户是否反感不断弹屏的功能设计。
基于WCF回调的双工通信,可以很好的完成报警推送。WCF中NetTcpBinding支持回调,因为从本质上讲TCP和IPC协议支持双向通信.
实现步骤:
1)首先定义报警服务接口(契约),提供订阅、注销、发布的外部接口功能。
namespace GpsNET { /** * Gps报警推送服务 * Author: http://cnblogs.com/productivity * */ [ServiceContract(SessionMode=SessionMode.Required, CallbackContract=typeof(IGpsServiceCallback))] interface IGpsEventService { /** * 订阅 * UserId 注册用户ID * Alarms 要订阅的报警类型ID * 注意IsOneWay = true,避免回调时发生死锁 */ [OperationContract(IsOneWay = true)] void Subscribe(int UserId, List<int> Alarms); //注销 [OperationContract(IsOneWay = true)] void Unsubscribe(int UserId); } }
2)定义GPS事件回调函数,当发生报警时,客户端会自动触发事件,关于IsOneWay = true这里就不多说了。
namespace GpsNET { /** * 报警回调 */ public interface IGpsServiceCallback { /** * msgItems 接收到的报警事件集合 */ [OperationContract(IsOneWay = true)] void OnMessageReceived(List<AlarmItem> msgItems); } }
3)报警服务实现
namespace GpsNET { /** * Gps报警推送服务 * Author: http://cnblogs.com/productivity * */ [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)] internal sealed class GpsEventService:IGpsEventService { protected static log4net.ILog logger = log4net.LogManager.GetLogger(typeof(GpsEventService)); public delegate void CallbackDelegate<T>(T t); //客户端的报警消息接收事件 public static CallbackDelegate<List<AlarmItem>> MessageReceived; //订阅者 public static List<AlarmSubscriber> Subscribers = new List<AlarmSubscriber>(); //用户订阅报警,Alarms代表要订阅的报警类型 public void Subscribe(int UserId, List<int> Alarms) { IGpsServiceCallback callback = OperationContext.Current.GetCallbackChannel<IGpsServiceCallback>(); User u = GetUser(UserId); AlarmSubscriber subscriber = GetSubscirber(UserId); if (subscriber == null) { subscriber = new AlarmSubscriber(); subscriber.User = u; Subscribers.Add(subscriber); logger.Info("客户端" + UserId + "注册"); } subscriber.Alarms = Alarms; //更新订阅 subscriber.ClientCallback = callback; //绑定退出事件,在客户端退出时,注销客户端的订阅 ICommunicationObject obj = (ICommunicationObject)callback; obj.Closed += new EventHandler(GpsEventService_Closed); obj.Closing += new EventHandler(GpsEventService_Closing); } private AlarmSubscriber GetSubscirber(int UserId) { foreach(AlarmSubscriber sub in Subscribers) { if (sub.User.Id == UserId) return sub; } return null; } private User GetUser(int UserId) { return new User(UserId); } void GpsEventService_Closing(object sender, EventArgs e) { logger.Info("客户端关闭退出..."); } void GpsEventService_Closed(object sender, EventArgs e) { IGpsServiceCallback callback = (IGpsServiceCallback)sender; Subscribers.ForEach(delegate(AlarmSubscriber subscriber) { if (subscriber.ClientCallback == callback) { Subscribers.Remove(subscriber); logger.Info("用户" + subscriber.User.Id + "Closed Client Removed!"); } }); } //客户端断开 public void Unsubscribe(int UserId) { IGpsServiceCallback callback = OperationContext.Current.GetCallbackChannel<IGpsServiceCallback>(); Subscribers.ForEach(delegate(AlarmSubscriber subscriber) { if (subscriber.User.Id == UserId) { Subscribers.Remove(subscriber); logger.Info("用户" + subscriber.User.Id + "注销 Client Removed!"); } }); } //向客户端推送报警数据 public static void SendAlarmMessage(List<AlarmItem> alarmItems) { //没有要推送的报警数据 if (alarmItems.Count == 0) return; Subscribers.ForEach(delegate(AlarmSubscriber subscriber) { ICommunicationObject callback = (ICommunicationObject)subscriber.ClientCallback; if (((ICommunicationObject)callback).State == CommunicationState.Opened) { try { //此处需要加上权限判断、订阅判断等 subscriber.ClientCallback.OnMessageReceived(alarmItems); } catch (Exception ex) { Subscribers.Remove(subscriber); logger.Error("用户" + subscriber.User.Id + "出错:" + ex.Message); logger.Error(ex.StackTrace); } } else { Subscribers.Remove(subscriber); logger.Info("用户" + subscriber.User.Id + "Closed Client Removed!"); } }); } //通知用户服务已经停止 public static void NotifyServiceStop() { List<AlarmItem> msgItems = new List<AlarmItem>(); msgItems.Add(new AlarmItem(0,"Stop")); SendAlarmMessage(msgItems); } } }
4)客户端调用
public partial class Form1 : Form, GpsAlarm.IGpsEventServiceCallback
{
int UserId = 1;
public Form1()
{
InitializeComponent();
}
GpsAlarm.GpsEventServiceClient client;
private void Form1_Load(object sender, EventArgs e)
{
try
{
client = new GpsAlarm.GpsEventServiceClient(new InstanceContext(this));//注意Form要实现接口
//注册并订阅报警类型是1,2,3
client.Subscribe(UserId, new int[]{1,2,3});
listBox1.Items.Add("注册成功,等待消息推送");
}
catch (Exception ex)
{
listBox1.Items.Add(ex.ToString());
}
}
#region IEventSystemCallback Members
/**
* 监听报警事件
*/
public void OnMessageReceived(AlarmItem[] msgItems)
{
foreach (AlarmItem mi in msgItems)
{
listBox1.Items.Add(mi.Name);
}
}
#endregion
}