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