基于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.<span style=
"color: #ff0000;"
><strong>IGpsEventServiceCallback</strong></span>
{
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
}
|