刘永斌,李宥谋,王 涛,赵成青
(西安邮电大学 计算机学院,陕西 西安 710000)
随着网络技术及信息技术的快速发展,仪器仪表的测控技术领域发生了巨大变化,出现了以LXI总线为基础的测控技术。LXI网络仪器是一种结构化、模块化的仪器系统,仪器中各模块之间、仪器与网络之间均通过LXI总线进行互联,这样可不受机箱和零槽控制器的制约,使得集成更方便,也使得仪器中各测量模块相互独立,具备模块化特点,突破了传统通信技术的时空和地域限制。而且通过代理使得不带网口的测量仪器网络化,使其大大超越了传统测试测量仪器的通讯性能及局限性。
而对LXI网络仪器的测控、监管与维护的前提是监管平台应提供良好的网络仪器拓扑图,通过良好的拓扑图既可对代理及其所接网络仪器提供直观的链接和分布等情况,也可提供操作简单、功能完善的测量和管理服务。因此LXI网络仪器拓扑图的发现、呈现与管理显得尤为重要。文中就拓扑图的动态生成、刷新以及对拓扑图上节点的状态和事件展开研究,并进行设计与实现。
整个LXI网络仪器[1]管理系统的数据库管理统一由监管平台处理,该系统的数据信息存储在名为xnmsdb的mysql5.5.54版本数据库中,其中包含的数据表是根据监管平台所提供的功能确定的,而网络仪器拓扑管理作为监管平台的一部分,涉及的关键数据表有代理编号与其MAC地址映射表(t_AgentIDMAC)和节点信息及拓扑关系表(t_RunningTreeView)。由于篇幅有限,只给出这两张表的结构设计。
t_AgentIDMAC的结构设计:
CREATE TABLE `t_AgentIDMAC` (
`id`int(11) NOT NULL,//代理编号,非空
`mac` varchar(20) NOT NULL, //代理MAC地址,非空,字符串类型
PRIMARY KEY (`id`), //id为主键
UNIQUE KEY `mac` (`mac`) //唯一性约束,防止MAC地址重复
)
t_RunningTreeView的结构设计:
CREATE TABLE `t_RunningTreeView` (
`id`int(11) NOT NULL AUTO_INCREMENT, //节点唯一标识号,自增
`name`varchar(20) NOT NULL, //节点名称,非空
`pid` int(11) NOT NULL, //本节点的父节点ID,非空
`ip` varchar(20) DEFAULT NULL, //代理IP地址
`mac` varchar(20) DEFAULT NULL, //代理MAC地址
`dscr` varchar(100) DEFAULT NULL, //节点信息描述
`interface`int(2) DEFAULT NULL, //代理的接口号
PRIMARY KEY (`id`) //id字段为主键
)
该监管平台开发环境是使用Windows OS中Microsoft Visual Studio 2012的IDE,利用C#[2]语言在.NET Framework4.5平台下进行开发。在C#的窗体应用程序开发时,要求在监管平台的主控界面上显示局域网中的网络仪器拓扑图[3]。拓扑是一种层次关系,而在面向组件的C#中,窗体开发时它本身提供的显示节点层级关系的控件只有TreeView控件,基于纵向的目录树结构,相当于书的目录样式,虽然功能完善,但是结构单一,不直观,不灵活,不能够适应特定的需求。所以对网络仪器拓扑图的管理需要有更符合人机操作关系的特定的拓扑图,那么就需要利用C#提供的图形设备接口GDI+[4]来绘制出更符合需求的拓扑结构。
拓扑图布局结构采用树状结构,整个LXI网络仪器管理系统最多有32个代理,每个代理最多可接4个测量仪器,根据实际情况可分为8组显示具体的拓扑图,每组可通过按钮分别显示。每组都为三级拓扑结构,根节点作为第一级,没有实际意义,起到连接代理的作用,代理为第二层节点,测量仪器为第三级节点,即叶子节点,连接在代理下。如果实际局域网中设备不能形成8组,那么只有存在的组的按钮才具备显示该组拓扑的事件。绘制拓扑图的关键是要把每组的三级拓扑层次结构绘制出来,并在拓扑周围绘制出必要的简易文字说明,绘制过程的控制中代码细节很多,这里只给出GDI+操作的主要步骤,对于每一组拓扑采用自上而下、从左到右的画法,如下:
(1)Graphics graphic=panel1.CreateGraphics();即在窗体form中的panel1面板上创建画板,接下来就可以在panel1上绘制图形、线条、文字等,但绘制的前提是要根据panel1的大小以及三级拓扑层次结构的大小及位置确定合理的X、Y坐标,确定绘制对象的长度、粗细、形状、颜色等属性。
(2)根据坐标和绘制对象的属性,利用Pen类实例化画笔对象,绘制根节点,形状为圆形,并利用上述的graphic[5]对象在圆形中绘制一个Image图形作为根节点标志;再根据根节点相对panel1位置绘制出相应的线条,用于连接四个代理。
(3)根据坐标和绘制对象属性,利用Pen类实例化画笔对象,通过for循环从左到右绘制出四个矩形,用来作为填充代理图标的框架。这里不需要急于绘制代理图标,因为还不知道实际有没有代理或存在哪些代理。再根据代理的矩形框图相对panel1位置绘制出相应的线条,用于连接16个测量仪器。
(4)类似步骤3,区别是for循环16次,每次绘制正方形,用作填充测量仪器图标的框架。
前面已将三级拓扑层次结构框图绘制出来,接下来需要在其基础上绘制出实际局域网中设备的图标,这样就能确定局域网中有哪些设备,以及这些设备的连接关系。前面已提到TreeView控件的局限性,所以将它作为辅助的拓扑显示,而将文中设计的拓扑用于主要的拓扑显示,该主要拓扑依赖于辅助拓扑的拓扑关系及数据,而该辅助拓扑依赖于数据表t_RunningTreeView。所以拓扑图的生成[6]步骤大致如下:
(1)生成TreeView。首先从t_RunningTreeView中读取数据存入DataTable内存数据表中,利用DataTable类的对象的Select方法,即DataRow[] dr=dt.Select("pid="+pid),通过foreach递归的从根节点,即pid为0的节点开始遍历dr;每次遍历实例化新的node对象,用于在TreeView中添加新节点,即TreeNode node=new TreeNode(),将dr对象的每行值与node的相关属性绑定,再把node通过Add方法添加到TreeView;在这个过程中还要进行总代理个数、总测量仪器个数等数据的统计。
(2)将TreeView中的信息保存到相应数组中,以便将TreeView辅助拓扑的拓扑关系及数据与主拓扑框图进行数据的绑定,即通过foreach语句递归地遍历TreeView中nodes节点,根据节点层次级别等属性判断,将node的信息存储到代理或测量仪器相关数组中,并做相关的数据统计,代码[7]如下:
private void tp_binding(TreeNodeCollection nodes)
{
foreach(TreeNode node in nodes)
{
if(node.Level==1) //代理
{
agentName[i1]=node.Text; //代理名
imgIndexAgent[i1]=node.ImageIndex;//代理图标索引
if(node.Nodes!=null)//该代理下还有孩子节点,即测量仪器
{
childNodeCount[k1]=node.Nodes.Count;//统计该代理下的仪器数
k1++;
sub_tp_binding(node.Nodes);//统计测量仪器相关数据
}
i1++;
m1++;
}
tp_binding(node.Nodes);//递归
}
}
private void sub_tp_binding(TreeNodeCollection nodes)
{
foreach(TreeNode childNode in nodes)//遍历代理下的测量仪器
if(childNode.Level==2) //测量仪器
{
devName[j1]=childNode.Text;//存储测量仪器名
imgIndexDev[j1]=childNode.ImageIndex;//测量仪器图标索引
j1++;
n1++;
}
sub_tp_binding(childNode.Nodes);//可以注释掉,因为没有四级节点
}
}
(3)根据所点击的某一组,获取该组索引,利用索引、代理框图的坐标及步骤2得到的节点信息,通过for循环依次绘制出四个代理图标,并绘制出代理名等文字说明,对于不存在的代理用专门的特定图标替代,不需要绘制代理名等文字说明;代理绘制完成后,统计所查看组的前面所有组代理所连接的测量仪器总数,即:for(int i=0;i<4*index;i++) j1+=childNodeCount[i];这样就知道了当前组中测量仪器图标的起始索引,然后结合该索引,通过双重循环(第一重循环为每个代理,第二重循环为每个代理下的四个测量仪器)和判断,依次绘制每个代理下所连接的测量仪器的图标,并绘制出代理名等文字说明,对不存在的测量仪器,用特定图标替代,不需要绘制代理名等文字说明。由于篇幅有限,不具体介绍代码,最后实现的拓扑是查看第一组拓扑显示的操作,见图1(左侧为TreeView目录树图,右侧就是基于TreeView的拓扑图)。
图1 网络仪器拓扑
拓扑图的刷新实质上是清除已有的TreeView目录树状图和拓扑图,再重新生成一次它们,基本原理还是根据t_RunningTreeView数据表中的数据生成TreeView目录树状图,再根据TreeView节点的数据绘制树状拓扑图。其缺点是无论拓扑图关系是否发生变化,就去刷新[8],人为控制时消耗人力,程序轮询控制时,在时间间隔上又有局限性。要实现动态刷新,刷新原理不变,区别是当局域网的代理或测量仪器连接状态的改变导致拓扑图改变时,更新t_RunningTreeView数据表,再进行程序控制的刷新。
获取[9]局域网中拓扑图连接状态改变的方法有三种,三种方法相互协调,共同完成各自的任务,缺一不可。由于代码较多,限于篇幅,这里只概述流程,分别为:
(1)DHCP自动发现[10]。这是后台模块调用的一个开源DHCP,目的是发现局域网[11]中的代理开发板,并为其分配IP地址,然后将IP地址和代理的MAC地址推送给监管平台,推送指的是后台和监管模块之间采用进程间的socket通信实现;监管平台专门创建并启动了一个后台线程去接收该推送的任务,接收到推送信息后,将线程将字节数组中的数据反序列化成对应的结构体,并将得到的有效数据显示在监管平台主控界面的运行栏中,还要写入日志文件,并通过MAC在t_AgentIDMAC数据表中查找对应代理编号,再根据IP在t_RunningTreeView表中查找对应的ID号,如果没找到,说明该代理不存在,需要将该代理信息添加至数据表t_RunningTreeView中,表明拓扑图连接状态已改变。反序列化和结构体[12]代码分别如下:
//对Socket收到的字节数组数据进行反序列化
public Object BytesToStruct(Byte[] bytes,Type strcutType)
{
int size=Marshal.SizeOf(strcutType);//得到结构体的大小
//分配结构体大小的内存空间
IntPtr buffer=Marshal.AllocHGlobal(size);
//从bytes字节数组拷贝到内存空间
Marshal.Copy(bytes,0,buffer,size);
//将数据从非托管内存封送到结构类型的托管对象
object obj=Marshal.PtrToStructure(buffer,strcutType);
Marshal.FreeHGlobal(buffer);//释放非托管内存空间
return obj; //返回对象
}
//反序列化后的数据结构为结构体,C#中结构的定义如下:
//引用C#的StructLayout特性,说明结构的数据字段的物理布局
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=0)]
public struct DHCPInfo
{
//MarshalAs特性表示如何在托管和非托管代码间封送数据
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=20)]
public stringMACAddress;//MAC地址字段
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=20)]
public stringIPAddress;//IP字段
}
(2)SNMP协议的Trap。代理Trap给监管平台的信息包括告警、事件和测量仪器的状态等信息,监管平台通过Trap的OID号进行字符串的解析和较为复杂的过滤、判断、映射及控制,获取测量仪器的插拔状态信息,更新t_RunningTreeView。Trap消息由代理模块发送给后台模块,后台模块进行一系列处理再通过socket套接字发送给监管模块。
(3)监管平台的扫描[13]代理线程。代理的上线有DHCP自动发现,测量仪器的插拔有代理的SNMP的Trap。对于代理的断线,首先它无法通过网络Trap,其次这里的DHCP不具备这样的功能,故只能依赖监管平台每隔一定时间去读取t_RunningTreeView表中所有代理的ID和IP,然后通过C#中Ping类和PingReply类来Ping每个IP,即发送ICMP回送请求,并获取ICMP应答状态,如果应答状态不成功,则表明该IP对应的代理不存在,应该从数据库[14]中删除该代理的记录并删除该代理下连接的全部测量仪器,即更新[15]数据表t_RunningTreeView,如果Ping通,ICMP应答状态成功,则读取下一个ID和IP,然后进行同样的操作。该扫描关键代码[16]如下:
Threadt_scan=new Thread(new ThreadStart(scanAgentIp));//创建线程
t_scan.Start();//启动线程
public voidscanAgentIp()
{
string sqlStr="select ip,id from t_RunningTreeView where pid=1";/* pid为1,表示代理,因为所有代理的父节点的id是1 */
string connectionString="server=localhost; user id=root; password=lyb; database=xnmsdb";//数据库连接的必要信息
conn_s=new MySqlConnection(connectionString);//实例化数据库连接对象
conn_s.Open(); //打开数据库连接
MySqlCommand cmd=conn_s.CreateCommand();
cmd.CommandText=sqlStr;
MySqlDataReader read=cmd.ExecuteReader();
while(read.Read())//读取下一个记录行
{
string ip=read.GetString(0); //获得该行第一个字段,即ip
int id=read.GetInt32(1); //得到id
Ping pingSender=new Ping();//实例化Ping类
PingReply reply=pingSender.Send(ip,250); //发送ICMP回送消息,并得到应答
if(reply.Status!=IPStatus.Success)//判断ICMP应答状态,如果不成功
{
string sqlStr1="delete from t_RunningTreeView where ip='"+ip + "' or pid='" + id + "'";//定义删除该代理及其所接的测量仪器的命令字符串
MyMysqlMeans.getsqlcom(sqlStr1);/*执行数据库操作,调用监管模块封装的数据库操作的静态类的相应静态方法 */
load_tp=new Thread(new ThreadStart(createAutoFind));
load_tp.Start();
}
pingSender.Dispose();//销毁对象
}
read.Dispose();
Thread.Sleep(3000); //隔3 s扫描一次
}
在上述三种方法中更新了t_RunningTreeView数据表后,就需要自动重新生成拓扑图,即调用绘制拓扑图所封装的方法[17],然而,这三种方式对应三个线程,这三个线程分别产生一个线程去专门执行重绘拓扑图的方法,如果再次产生的这三个线程都去调用该方法更新UI界面的拓扑图,会产生错误。C#中不允许这样的操作,所以要声明委托,在线程上执行指定的委托。关键代码如下:
Thread load_tp=new Thread(new ThreadStart(createAutoFind));
load_tp.Start();
public delegate voidmy_autoFind();//声明委托
public voidcreateAutoFind()
{
this.Invoke(new my_autoFind(autoFindStart));// 在线程上执行指定的委托
}
节点操作事件是对拓扑图中的代理和测量仪器进行符合需求的各种操作,即在代理或测量仪器的图标区域通过单击鼠标右键形成各自的菜单栏,再根据菜单栏选定某一菜单项,完成该菜单项所提供的设备管理功能。panel1面板鼠标单击事件的注册代码如下:
this.panel1.MouseClick+=
newSystem.Windows.Forms.MouseEventHandler(this.panel1_MouseClick);
对于实现panel1_MouseClick(object sender,MouseEventArgs e)方法的代码细节较多,这里只给出该方法的算法描述:
首先根据e对象判断单击的是哪个鼠标按钮,如果是左键,方法调用结束,否则对每一组拓扑中代理和测量仪器所占用的20个区域,逐个判断e对象的X、Y坐标是否在这些区域中,如果在(表明就不需要判断其他区域了,程序完成了整个功能后可以通过break跳出循环,并结束),计算出当前点击的是第j索引组拓扑图。然后再判断鼠标右击的第i索引区域编号是否小于4,如果是,表明该区域是代理,否则该区域是测量仪器;其中i取值范围是-1
如果是代理区域,即i<4,需用i<(agentNum-4*j)判断该区域是否存在代理,如果存在,通过index=i+4*j计算出该代理在整个拓扑中的索引,最后根据该索引在该代理区域周围弹出菜单栏,即agentMenu.Show(e.X+250,e.Y+150),程序结束;其中agentNum表示存在的代理总数。
如果是测量仪器区域,要用index=4*j+(i-4)/4计算出该区域是否属于index索引的代理下的区域,需用(index+1)<=agentNum判断该区域附属的索引为index的代理区域是否存在代理。如果存在,通过node=treeView1.Nodes[0].Nodes[index]获得TreeView控件中索引为index的代理节点,如果该代理节点下还有节点(测量仪器),通过index1=i%4计算出右击的测量仪器区域相对其附属代理下的4个区域中的索引号,再通过index1 文中研究了局域网中网络仪器拓扑图的结构设计和自动发现、生成以及节点的右击事件,属于LXI网络仪器测控与管理系统项目中监管平台的一部分。该项目具备研究与实现的意义与价值,也具备创新性与实用性;该项目已被验收,运行正常,达到了预期目标。4 结束语