实战解析--主体实现和界面示意
在上篇中细心的读者会发现以下两句是有问题的:
_timer=new System.Timers.Timer(TIMER_INTRVAL);
_timer.Elapsed+=new System.Timers.ElapsedEventHandler(timer_Elapsed);
就是因为这一句,我们的规则处理器的实例[比如数据线性转换f(x)=a.x+c ]哪怕启动两个也没有用,因为我们的容器是在单线程中执行,所以这两个规则处理实例的时间触发时刻非常接近。所以当第一个的时间点到时,第一个实例将会处理完队列中的所有待处理数据。等到第二个实例的处理代码得以执行时,队列可能已经是空的了。
当然可以在new System.Timers.Timer(TIMER_INTRVAL)时加上一个随机数来让这两个实例的触发时刻分开;或者使用每个实例运行在单独的线程上真正互不干扰;或者使用事件通知而取消时间轮询。总之,技术上可以有很多办法达到一个目标。
言归正传。我们的监控系统核心稳定的数据协议可以定义类似下面:
namespace OpenMonitor.Data {
using System;
using System.Collections;
using System.Text;
using System.Collections.Specialized;
using System.Xml.Serialization;
[Serializable]
[XmlRoot("DATA")]
public class InfoData:EventArgs{
[XmlAttribute("ID")]
public string ID;
[XmlAttribute("Time")]
public string Time;//HH:MM
[XmlAttribute("Value")]
public string Value;
[XmlAttribute("State")]
public string State;
public InfoData() {
}
public InfoData(string id,object val) {
ID=id;
Value=val.ToString();
DateTime dt=DateTime.Now;
Time=dt.Hour.ToString("D2")+":"+dt.Minute.ToString("D2")+":"+dt.Second.ToString("D2");
State="?";
}
public InfoData(InfoData data) {
this.ID=data.ID;
this.Time=data.Time;
this.Value=data.Value;
this.State=data.State;
}
public override string ToString() {
return "["+ID+"]("+this.State+")===>"+Value;
}
}
[Serializable]
[XmlRoot("DATA")]
public class DataPacket {
[NonSerialized()]
private IDictionary ht;
public DataPacket() {
ht=new System.Collections.Specialized.HybridDictionary();
}
[XmlArray(ElementName="InfoDatas")]
[XmlArrayItem(ElementName="InfoData", Type=typeof(InfoData))]
public InfoData[] Infos {
get{
InfoData[] rt=new InfoData[ht.Count];
int i=0;
foreach(InfoData p in ht.Values) rt[i++]=p;
return rt;
}
set {
ht.Clear();
foreach(InfoData d in value) add(d);
}
}
public string this[string Key] {
get{
foreach(InfoData s in ht.Values) if(s.ID==Key) return s.Value;
return String.Empty;
}
}
public void add(InfoData info) {
if(ht.Contains(info.ID)) ht[info.ID]=info;
else ht.Add(info.ID,info);
}
public int Count {
get {return ht.Count;}
}
public void clear() {
ht.Clear();
}
}
}
这是这个系统比较稳定的地方,大量的通讯数据就是这样定义的,很简单吧。这些数据应该是从底往上走,我们再来看看从上往下发的控制命令的协议定义:
namespace OpenMonitor.Data {
using System;
using System.Collections;
using System.Text;
using System.Collections.Specialized;
using System.Xml.Serialization;
[Serializable]
[XmlRoot("DATA")]
public class CmdData:EventArgs{
public string Operator;
public string Time;
public string CommandName;
public string Command;
public CmdData() {
}
public CmdData(string cmdName,string cmd,string op) {
DateTime dt=DateTime.Now;
Time=dt.Hour.ToString("D2")+":"+dt.Minute.ToString("D2")+":"+dt.Second.ToString("D2");
CommandName=cmdName;
Operator=op;
Command=cmd;
}
public CmdData(CmdData data) {
this.Operator=data.Operator;
this.CommandName=data.CommandName;
this.Command=data.Command;
this.Time=data.Time;
}
public override string ToString() {
return "from "+Operator+"["+Time+"]===>"+Command;
}
}
}
这些原始的数据和加工后的数据将要送到数据队列中,对于商业应用。我们可以选择IBM,MS等的产品。在此我们可以用一个简化的实现来看看核心的部分:
1
2namespace OpenMonitor.Utility {
3 using System;
4
5 using System.Collections;
6 using OpenMonitor;
7
8
9 public class SimpleQueue :IMessageQueue {
10 private IDictionary _catalogs = Hashtable.Synchronized(new Hashtable());
11
12
13 private ILogService log;
14
15 private void init() {
16 log=DefaultLogger.INSTANCE;
17
18 }
19 public SimpleQueue() {
20 init();
21 DefaultLogger.INSTANCE.DEBUG("SimpleQueue created!");
22 }
23
24
25 private Queue getQueue(string catalog) {
26 if (!_catalogs.Contains(catalog))
27 _catalogs.Add(catalog,Queue.Synchronized(new Queue()));
28 return _catalogs[catalog] as Queue;
29 }
30 public string[] getQueueNames() {
31 string[] rt=new string[_catalogs.Count];
32 int i=0;
33 foreach(string key in _catalogs.Keys)
34 rt[i++]=key;
35 return rt;
36
37 }
38
39 public string[] getQueueInfo(string QueueName) {
40 Queue q=getQueue(QueueName);
41 object[] objs= q.ToArray();
42 string[] rt=new string[objs.Length];
43 for(int i=0;i<objs.Length;i++)
44 rt[i]=objs[i].ToString();
45 return rt;
46
47 }
48 public void addDataToQueue(string Catalog,object XmlData) {
49 getQueue(Catalog).Enqueue(XmlData);
50 //log.DEBUG(string.Format("addDataToQueue({0})",Catalog));
51 }
52 public object getDataFromQueue(string Catalog) {
53 object rt = null;
54 Queue q=getQueue(Catalog);
55 if (q.Count>0) {
56 rt=q.Dequeue();
57 }
58 return rt ;
59 }
60 }
61}
62
2namespace OpenMonitor.Utility {
3 using System;
4
5 using System.Collections;
6 using OpenMonitor;
7
8
9 public class SimpleQueue :IMessageQueue {
10 private IDictionary _catalogs = Hashtable.Synchronized(new Hashtable());
11
12
13 private ILogService log;
14
15 private void init() {
16 log=DefaultLogger.INSTANCE;
17
18 }
19 public SimpleQueue() {
20 init();
21 DefaultLogger.INSTANCE.DEBUG("SimpleQueue created!");
22 }
23
24
25 private Queue getQueue(string catalog) {
26 if (!_catalogs.Contains(catalog))
27 _catalogs.Add(catalog,Queue.Synchronized(new Queue()));
28 return _catalogs[catalog] as Queue;
29 }
30 public string[] getQueueNames() {
31 string[] rt=new string[_catalogs.Count];
32 int i=0;
33 foreach(string key in _catalogs.Keys)
34 rt[i++]=key;
35 return rt;
36
37 }
38
39 public string[] getQueueInfo(string QueueName) {
40 Queue q=getQueue(QueueName);
41 object[] objs= q.ToArray();
42 string[] rt=new string[objs.Length];
43 for(int i=0;i<objs.Length;i++)
44 rt[i]=objs[i].ToString();
45 return rt;
46
47 }
48 public void addDataToQueue(string Catalog,object XmlData) {
49 getQueue(Catalog).Enqueue(XmlData);
50 //log.DEBUG(string.Format("addDataToQueue({0})",Catalog));
51 }
52 public object getDataFromQueue(string Catalog) {
53 object rt = null;
54 Queue q=getQueue(Catalog);
55 if (q.Count>0) {
56 rt=q.Dequeue();
57 }
58 return rt ;
59 }
60 }
61}
62
这个系统的主体模块是DataHub,以下是主要片断:
namespace OpenMonitor {
using System;
using System.Collections;
using System.Text;
using QPG.Net;
using OpenMonitor.Utility;
using OpenMonitor.Data;
using OpenMonitor.Services;
public class DataHub {
protected SiteConfigPacket _cfg;//保存本地的采集点的配置信息
private IServerChannel _server;//接收数据采集器的通道
private QPG.Net.IClientChannel _client;//作为客户端报送数据的通道
private IMessageQueue _queue;
private ILogService log=DefaultLogger.INSTANCE;
private DefaultDataTransform dt;
protected System.Collections.IDictionary _map;//用于索引信息点所在的通道号
private lookForHandler _cmdFactory;
public event StateChangedCallback OnServerStateChanged;
public event DataReceivedCallback OnServerReceivedData;
public virtual lookForHandler CommandFactoryMethod{
get{return _cmdFactory;}
set{ _cmdFactory=value;}
}
public virtual IServerChannel ServerChannel{
get{return _server;}
}
public virtual IClientChannel ClientChannel{
get{return _client;}
set{_client=value;}
}
public virtual IMessageQueue MQ{
get{return _queue ;}
}
public virtual string HubCode{
get{return _cfg.SiteCode ;}
}
public string HubName{
get{return _cfg.SiteName ;}
}
public DataHub(IServerChannel collect_svr,IMessageQueue queue, SiteConfigPacket cfg) {
_server=collect_svr;
_queue=queue;
_cfg=cfg;
init();
_map=new Hashtable();
log.DEBUG("DataHub "+HubName+" created!");
}
public virtual void sendCmd(string id,CmdData cmd) {
string cmdtext=dt.toXml(cmd);
int cnum=getChannelNum(id);
if(cnum>0) _server.sendMsgToClient(cmdtext,cnum);
else _server.sendMsgToAll(cmdtext);
}
public virtual void addMapItem(string id,int ChannelNum) {
if(_map.Contains(id)) _map[id]=ChannelNum;
else _map.Add(id,ChannelNum);
}
public virtual int getChannelNum(string id) {
if(_map.Contains(id)) return (int)_map[id];
return 0;//不存在!
}
private void init() {
//log.DEBUG("Memory Message Queue Started (SocketServerPort:"+_server.Port+")");
_server.OnConnected+=new StateChangedCallback(OnConnected);
_server.OnDisconnect+=new StateChangedCallback(OnDisconnect);
_server.OnReceived+=new DataReceivedCallback(OnReceived);
dt=new DefaultDataTransform();
_server.open();
}
private void OnConnected(string text) {
if(OnServerStateChanged!=null) OnServerStateChanged(text);
}
private void OnDisconnect(string text) {
if(OnServerStateChanged!=null) OnServerStateChanged(text);
}
// private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
// string[] ServiceNames=_queue.getQueueNames();
// for(int i=0;i<ServiceNames.Length;i++) {
// Console.WriteLine(ServiceNames[i]+":");
// string[] val=_queue.getQueueInfo(ServiceNames[i]);
// for(int j=0;j<val.Length;j++) Console.WriteLine(val[j]);
// }
// }
private void handleInfoData(string text,int curSocketIndex) {
DataPacket packet=dt.toObject(text,typeof(DataPacket)) as DataPacket;
InfoData[] data=packet.Infos;
foreach(InfoData info in data) {
addMapItem(info.ID,curSocketIndex);
if(_cfg[info.ID]==null||_cfg[info.ID].RuleConfigs.Length<1) _queue.addDataToQueue(ResultService.ServiceName,info);
else _queue.addDataToQueue(_cfg[info.ID].RuleConfigs[0].Name,info);
}
}
private void handleData(string text, int curSocketIndex) {
if(text.IndexOf("InfoDatas")>=0) handleInfoData(text,curSocketIndex);
else if(text.IndexOf("CommandName")>=0) {
CmdData cmd=dt.toObject(text,typeof(CmdData)) as CmdData;
IDictionary dic=InfoConfig.getParameterDictionary(cmd.Command);
dic.Add("CurSocketIndex",curSocketIndex);
try{
IRule hander=CommandFactoryMethod(cmd.CommandName);
if(hander!=null) hander.handle(this,dic);
}
catch{
log.ERROR(text,new Exception("找不到命令处理程序"));
}
}
}
private void OnReceived(string text, int curSocketIndex) {
try{
string msg=string.Format("数据采集器({0})发来数据",curSocketIndex)+System.Environment.NewLine;
if(OnServerReceivedData!=null) OnServerReceivedData(msg+text,curSocketIndex);
handleData(text, curSocketIndex);
}
catch{
log.ERROR(text,new Exception("不是完整XML"));
}
}
}
}
我们把DataHub加载到容器中,再给一个控件显示其最新收到的数据,效果如下:
由于我们采用的是开放的XML+socket.所以任何符合上面格式的数据都可以把数据汇总到DataHub.
DataHub收到数据后,就可以把数据进行加工处理,必要时要过滤,否则越到上面数据量就越多,对于没有变化的数据,我们为什么要耗费资源呢?
订阅者收到数据后,可以发挥你的想象力了,下面是一个展示:
今后的维护工作有两部分:一是增加已有类型的信息点,这很简单,修改配置文件如下:
<?xml version="1.0" encoding="utf-16"?>
<DATA xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SiteCode>HN</SiteCode>
<SiteName>湖南</SiteName>
<InfoConfigs>
<InfoConfig ID="01" Code="A01">
<Name>信号1</Name>
<Rules>
<Rule Name="linearity">
<Parameters>a=30;k=0</Parameters>
</Rule>
<Rule Name="userscope">
<Parameters>NormalLow=100;NormalHigh=200</Parameters>
</Rule>
</Rules>
</InfoConfig>
<InfoConfig ID="02" Code="A02">
<Name>信号2</Name>
<Rules>
<Rule Name="linearity">
<Parameters>a=30;k=0</Parameters>
</Rule>
<Rule Name="sysscope">
<Parameters></Parameters>
</Rule>
</Rules>
</InfoConfig>
</InfoConfigs>
</DATA>
<DATA xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SiteCode>HN</SiteCode>
<SiteName>湖南</SiteName>
<InfoConfigs>
<InfoConfig ID="01" Code="A01">
<Name>信号1</Name>
<Rules>
<Rule Name="linearity">
<Parameters>a=30;k=0</Parameters>
</Rule>
<Rule Name="userscope">
<Parameters>NormalLow=100;NormalHigh=200</Parameters>
</Rule>
</Rules>
</InfoConfig>
<InfoConfig ID="02" Code="A02">
<Name>信号2</Name>
<Rules>
<Rule Name="linearity">
<Parameters>a=30;k=0</Parameters>
</Rule>
<Rule Name="sysscope">
<Parameters></Parameters>
</Rule>
</Rules>
</InfoConfig>
</InfoConfigs>
</DATA>
但是如果增加了新的数据类型或者规则,那就要改程序了,也不难,看看我的线形处理器代码,保证你会写了:
1namespace OpenMonitor.Services {
2 using System;
3 using System.Collections;
4
5
6 using OpenMonitor.Data;
7 using OpenMonitor.Utility;
8
9
10 public class LinearityFormula:BaseService {
11
12
13 public LinearityFormula(IMessageQueue mq,SiteConfigPacket cfg):base("linearity",mq,cfg) {
14
15 }
16
17
18 public override void handle(object data) {
19 InfoData info=new InfoData(data as InfoData);
20 string old=info.Value;
21 InfoConfig cfg=_cfg[info.ID];
22
23 IDictionary d=InfoConfig.getParameterDictionary(cfg.getCurRule(this.ServiceName).Parameters);
24 double v=double.Parse(d["a"].ToString())*double.Parse(info.Value)+double.Parse(d["k"].ToString());
25 info.Value=v.ToString();
26 ToNextRule(info,cfg);
27 DefaultLogger.INSTANCE.DEBUG(this.GetType().Name+":"+d["a"].ToString()+"×"+old+"+"+d["k"].ToString()+"-->"+info);
28
29 }
30 }
31}
32
2 using System;
3 using System.Collections;
4
5
6 using OpenMonitor.Data;
7 using OpenMonitor.Utility;
8
9
10 public class LinearityFormula:BaseService {
11
12
13 public LinearityFormula(IMessageQueue mq,SiteConfigPacket cfg):base("linearity",mq,cfg) {
14
15 }
16
17
18 public override void handle(object data) {
19 InfoData info=new InfoData(data as InfoData);
20 string old=info.Value;
21 InfoConfig cfg=_cfg[info.ID];
22
23 IDictionary d=InfoConfig.getParameterDictionary(cfg.getCurRule(this.ServiceName).Parameters);
24 double v=double.Parse(d["a"].ToString())*double.Parse(info.Value)+double.Parse(d["k"].ToString());
25 info.Value=v.ToString();
26 ToNextRule(info,cfg);
27 DefaultLogger.INSTANCE.DEBUG(this.GetType().Name+":"+d["a"].ToString()+"×"+old+"+"+d["k"].ToString()+"-->"+info);
28
29 }
30 }
31}
32
好了,经过一周的努力,终于兑现了我给各位读者的承诺,请关注我的最后一篇<<总结>>吧。