基於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
}