System.Timers.Timer(TIMER_INTRVAL)时加上一个随机数来让这两个实例的触发时刻分开;或者使用每个实例运行在单独的线程上真正互不干扰;或者使用事件通知而取消时间轮询。总之,技术上可以有很多办法达到一个目标。
言归正传。我们的监控系统核心稳定的数据协议可以定义类似下面:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace OpenMonitor.Data
{
using System;
using System.Collections;
using System.Text;
using System.Collections.Specialized;
using System.Xml.Serialization;
![](/Images/OutliningIndicators/InBlock.gif)
[Serializable]
[XmlRoot("DATA")]
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class InfoData:EventArgs
{
[XmlAttribute("ID")]
public string ID;
![](/Images/OutliningIndicators/InBlock.gif)
[XmlAttribute("Time")]
public string Time;//HH:MM
[XmlAttribute("Value")]
public string Value;
[XmlAttribute("State")]
public string State;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public InfoData()
{
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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="?";
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public InfoData(InfoData data)
{
this.ID=data.ID;
this.Time=data.Time;
this.Value=data.Value;
this.State=data.State;
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public override string ToString()
{
return "["+ID+"]("+this.State+")===>"+Value;
}
![](/Images/OutliningIndicators/InBlock.gif)
}
[Serializable]
[XmlRoot("DATA")]
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class DataPacket
{
[NonSerialized()]
private IDictionary ht;
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public DataPacket()
{
ht=new System.Collections.Specialized.HybridDictionary();
}
[XmlArray(ElementName="InfoDatas")]
[XmlArrayItem(ElementName="InfoData", Type=typeof(InfoData))]
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public InfoData[] Infos
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{
InfoData[] rt=new InfoData[ht.Count];
int i=0;
foreach(InfoData p in ht.Values) rt[i++]=p;
return rt;
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
set
{
ht.Clear();
foreach(InfoData d in value) add(d);
}
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string this[string Key]
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{
foreach(InfoData s in ht.Values) if(s.ID==Key) return s.Value;
return String.Empty;
}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void add(InfoData info)
{
if(ht.Contains(info.ID)) ht[info.ID]=info;
else ht.Add(info.ID,info);
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public int Count
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return ht.Count;}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void clear()
{
ht.Clear();
}
}
![](/Images/OutliningIndicators/InBlock.gif)
}
![](/Images/OutliningIndicators/None.gif)
这是这个系统比较稳定的地方,大量的通讯数据就是这样定义的,很简单吧。这些数据应该是从底往上走,我们再来看看从上往下发的控制命令的协议定义:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace OpenMonitor.Data
{
using System;
using System.Collections;
using System.Text;
using System.Collections.Specialized;
using System.Xml.Serialization;
![](/Images/OutliningIndicators/InBlock.gif)
[Serializable]
[XmlRoot("DATA")]
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class CmdData:EventArgs
{
public string Operator;
![](/Images/OutliningIndicators/InBlock.gif)
public string Time;
![](/Images/OutliningIndicators/InBlock.gif)
public string CommandName;
public string Command;
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public CmdData()
{
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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;
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public CmdData(CmdData data)
{
this.Operator=data.Operator;
this.CommandName=data.CommandName;
this.Command=data.Command;
this.Time=data.Time;
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public override string ToString()
{
return "from "+Operator+"["+Time+"]===>"+Command;
}
}
}
![](/Images/OutliningIndicators/None.gif)
这些原始的数据和加工后的数据将要送到数据队列中,对于商业应用。我们可以选择IBM,MS等的产品。在此我们可以用一个简化的实现来看看核心的部分:
1![](/Images/OutliningIndicators/None.gif)
2![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace OpenMonitor.Utility
{
3
using System;
4
5
using System.Collections;
6
using OpenMonitor;
7![](/Images/OutliningIndicators/InBlock.gif)
8
9![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class SimpleQueue :IMessageQueue
{
10
private IDictionary _catalogs = Hashtable.Synchronized(new Hashtable());
11
12![](/Images/OutliningIndicators/InBlock.gif)
13
private ILogService log;
14![](/Images/OutliningIndicators/InBlock.gif)
15![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void init()
{
16
log=DefaultLogger.INSTANCE;
17
18
}
19![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public SimpleQueue()
{
20
init();
21
DefaultLogger.INSTANCE.DEBUG("SimpleQueue created!");
22
}
23![](/Images/OutliningIndicators/InBlock.gif)
24
25![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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![](/Images/OutliningIndicators/InBlock.gif)
37
}
38![](/Images/OutliningIndicators/InBlock.gif)
39![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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![](/Images/OutliningIndicators/InBlock.gif)
47
}
48![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void addDataToQueue(string Catalog,object XmlData)
{
49
getQueue(Catalog).Enqueue(XmlData);
50
//log.DEBUG(string.Format("addDataToQueue({0})",Catalog));
51
}
52![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public object getDataFromQueue(string Catalog)
{
53
object rt = null;
54
Queue q=getQueue(Catalog);
55![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (q.Count>0)
{
56
rt=q.Dequeue();
57
}
58
return rt ;
59
}
60
}
61
}
62![](/Images/OutliningIndicators/None.gif)
这个系统的主体模块是DataHub,以下是主要片断:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace OpenMonitor
{
using System;
using System.Collections;
using System.Text;
![](/Images/OutliningIndicators/InBlock.gif)
using QPG.Net;
using OpenMonitor.Utility;
using OpenMonitor.Data;
using OpenMonitor.Services;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class DataHub
{
protected SiteConfigPacket _cfg;//保存本地的采集点的配置信息
![](/Images/OutliningIndicators/InBlock.gif)
private IServerChannel _server;//接收数据采集器的通道
private QPG.Net.IClientChannel _client;//作为客户端报送数据的通道
private IMessageQueue _queue;
![](/Images/OutliningIndicators/InBlock.gif)
private ILogService log=DefaultLogger.INSTANCE;
private DefaultDataTransform dt;
protected System.Collections.IDictionary _map;//用于索引信息点所在的通道号
private lookForHandler _cmdFactory;
![](/Images/OutliningIndicators/InBlock.gif)
public event StateChangedCallback OnServerStateChanged;
public event DataReceivedCallback OnServerReceivedData;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual lookForHandler CommandFactoryMethod
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return _cmdFactory;}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
set
{ _cmdFactory=value;}
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual IServerChannel ServerChannel
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return _server;}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual IClientChannel ClientChannel
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return _client;}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
set
{_client=value;}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual IMessageQueue MQ
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return _queue ;}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual string HubCode
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return _cfg.SiteCode ;}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public string HubName
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{return _cfg.SiteName ;}
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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!");
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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);
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual void addMapItem(string id,int ChannelNum)
{
if(_map.Contains(id)) _map[id]=ChannelNum;
else _map.Add(id,ChannelNum);
![](/Images/OutliningIndicators/InBlock.gif)
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public virtual int getChannelNum(string id)
{
if(_map.Contains(id)) return (int)_map[id];
return 0;//不存在!
![](/Images/OutliningIndicators/InBlock.gif)
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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();
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void OnConnected(string text)
{
if(OnServerStateChanged!=null) OnServerStateChanged(text);
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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]);
// }
// }
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void handleInfoData(string text,int curSocketIndex)
{
DataPacket packet=dt.toObject(text,typeof(DataPacket)) as DataPacket;
InfoData[] data=packet.Infos;
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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);
}
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void handleData(string text, int curSocketIndex)
{
if(text.IndexOf("InfoDatas")>=0) handleInfoData(text,curSocketIndex);
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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);
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
try
{
IRule hander=CommandFactoryMethod(cmd.CommandName);
if(hander!=null) hander.handle(this,dic);
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
catch
{
log.ERROR(text,new Exception("找不到命令处理程序"));
}
}
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void OnReceived(string text, int curSocketIndex)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
try
{
string msg=string.Format("数据采集器({0})发来数据",curSocketIndex)+System.Environment.NewLine;
if(OnServerReceivedData!=null) OnServerReceivedData(msg+text,curSocketIndex);
handleData(text, curSocketIndex);
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
catch
{
log.ERROR(text,new Exception("不是完整XML"));
}
}
}
}
![](/Images/OutliningIndicators/None.gif)
我们把DataHub加载到容器中,再给一个控件显示其最新收到的数据,效果如下:
![](/images/cnblogs_com/qpg2006/DataHub.jpg)
![](/images/cnblogs_com/qpg2006/DataHub2.jpg)
由于我们采用的是开放的XML+socket.所以任何符合上面格式的数据都可以把数据汇总到DataHub.
DataHub收到数据后,就可以把数据进行加工处理,必要时要过滤,否则越到上面数据量就越多,对于没有变化的数据,我们为什么要耗费资源呢?
订阅者收到数据后,可以发挥你的想象力了,下面是一个展示:
![](/images/cnblogs_com/qpg2006/Gauge.jpg)
今后的维护工作有两部分:一是增加已有类型的信息点,这很简单,修改配置文件如下:
<?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>
但是如果增加了新的数据类型或者规则,那就要改程序了,也不难,看看我的线形处理器代码,保证你会写了:
1![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace OpenMonitor.Services
{
2
using System;
3
using System.Collections;
4![](/Images/OutliningIndicators/InBlock.gif)
5![](/Images/OutliningIndicators/InBlock.gif)
6
using OpenMonitor.Data;
7
using OpenMonitor.Utility;
8![](/Images/OutliningIndicators/InBlock.gif)
9
10![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class LinearityFormula:BaseService
{
11
12
13![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public LinearityFormula(IMessageQueue mq,SiteConfigPacket cfg):base("linearity",mq,cfg)
{
14
15
}
16
17
18![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
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![](/Images/OutliningIndicators/None.gif)
好了,经过一周的努力,终于兑现了我给各位读者的承诺,请关注我的最后一篇<<总结>>吧。