劉永斌,李宥謀,王 濤,趙成青
(西安郵電大學(xué) 計算機學(xué)院,陜西 西安 710000)
隨著網(wǎng)絡(luò)技術(shù)及信息技術(shù)的快速發(fā)展,儀器儀表的測控技術(shù)領(lǐng)域發(fā)生了巨大變化,出現(xiàn)了以LXI總線為基礎(chǔ)的測控技術(shù)。LXI網(wǎng)絡(luò)儀器是一種結(jié)構(gòu)化、模塊化的儀器系統(tǒng),儀器中各模塊之間、儀器與網(wǎng)絡(luò)之間均通過LXI總線進行互聯(lián),這樣可不受機箱和零槽控制器的制約,使得集成更方便,也使得儀器中各測量模塊相互獨立,具備模塊化特點,突破了傳統(tǒng)通信技術(shù)的時空和地域限制。而且通過代理使得不帶網(wǎng)口的測量儀器網(wǎng)絡(luò)化,使其大大超越了傳統(tǒng)測試測量儀器的通訊性能及局限性。
而對LXI網(wǎng)絡(luò)儀器的測控、監(jiān)管與維護的前提是監(jiān)管平臺應(yīng)提供良好的網(wǎng)絡(luò)儀器拓?fù)鋱D,通過良好的拓?fù)鋱D既可對代理及其所接網(wǎng)絡(luò)儀器提供直觀的鏈接和分布等情況,也可提供操作簡單、功能完善的測量和管理服務(wù)。因此LXI網(wǎng)絡(luò)儀器拓?fù)鋱D的發(fā)現(xiàn)、呈現(xiàn)與管理顯得尤為重要。文中就拓?fù)鋱D的動態(tài)生成、刷新以及對拓?fù)鋱D上節(jié)點的狀態(tài)和事件展開研究,并進行設(shè)計與實現(xiàn)。
整個LXI網(wǎng)絡(luò)儀器[1]管理系統(tǒng)的數(shù)據(jù)庫管理統(tǒng)一由監(jiān)管平臺處理,該系統(tǒng)的數(shù)據(jù)信息存儲在名為xnmsdb的mysql5.5.54版本數(shù)據(jù)庫中,其中包含的數(shù)據(jù)表是根據(jù)監(jiān)管平臺所提供的功能確定的,而網(wǎng)絡(luò)儀器拓?fù)涔芾碜鳛楸O(jiān)管平臺的一部分,涉及的關(guān)鍵數(shù)據(jù)表有代理編號與其MAC地址映射表(t_AgentIDMAC)和節(jié)點信息及拓?fù)潢P(guān)系表(t_RunningTreeView)。由于篇幅有限,只給出這兩張表的結(jié)構(gòu)設(shè)計。
t_AgentIDMAC的結(jié)構(gòu)設(shè)計:
CREATE TABLE `t_AgentIDMAC` (
`id`int(11) NOT NULL,//代理編號,非空
`mac` varchar(20) NOT NULL, //代理MAC地址,非空,字符串類型
PRIMARY KEY (`id`), //id為主鍵
UNIQUE KEY `mac` (`mac`) //唯一性約束,防止MAC地址重復(fù)
)
t_RunningTreeView的結(jié)構(gòu)設(shè)計:
CREATE TABLE `t_RunningTreeView` (
`id`int(11) NOT NULL AUTO_INCREMENT, //節(jié)點唯一標(biāo)識號,自增
`name`varchar(20) NOT NULL, //節(jié)點名稱,非空
`pid` int(11) NOT NULL, //本節(jié)點的父節(jié)點ID,非空
`ip` varchar(20) DEFAULT NULL, //代理IP地址
`mac` varchar(20) DEFAULT NULL, //代理MAC地址
`dscr` varchar(100) DEFAULT NULL, //節(jié)點信息描述
`interface`int(2) DEFAULT NULL, //代理的接口號
PRIMARY KEY (`id`) //id字段為主鍵
)
該監(jiān)管平臺開發(fā)環(huán)境是使用Windows OS中Microsoft Visual Studio 2012的IDE,利用C#[2]語言在.NET Framework4.5平臺下進行開發(fā)。在C#的窗體應(yīng)用程序開發(fā)時,要求在監(jiān)管平臺的主控界面上顯示局域網(wǎng)中的網(wǎng)絡(luò)儀器拓?fù)鋱D[3]。拓?fù)涫且环N層次關(guān)系,而在面向組件的C#中,窗體開發(fā)時它本身提供的顯示節(jié)點層級關(guān)系的控件只有TreeView控件,基于縱向的目錄樹結(jié)構(gòu),相當(dāng)于書的目錄樣式,雖然功能完善,但是結(jié)構(gòu)單一,不直觀,不靈活,不能夠適應(yīng)特定的需求。所以對網(wǎng)絡(luò)儀器拓?fù)鋱D的管理需要有更符合人機操作關(guān)系的特定的拓?fù)鋱D,那么就需要利用C#提供的圖形設(shè)備接口GDI+[4]來繪制出更符合需求的拓?fù)浣Y(jié)構(gòu)。
拓?fù)鋱D布局結(jié)構(gòu)采用樹狀結(jié)構(gòu),整個LXI網(wǎng)絡(luò)儀器管理系統(tǒng)最多有32個代理,每個代理最多可接4個測量儀器,根據(jù)實際情況可分為8組顯示具體的拓?fù)鋱D,每組可通過按鈕分別顯示。每組都為三級拓?fù)浣Y(jié)構(gòu),根節(jié)點作為第一級,沒有實際意義,起到連接代理的作用,代理為第二層節(jié)點,測量儀器為第三級節(jié)點,即葉子節(jié)點,連接在代理下。如果實際局域網(wǎng)中設(shè)備不能形成8組,那么只有存在的組的按鈕才具備顯示該組拓?fù)涞氖录?。繪制拓?fù)鋱D的關(guān)鍵是要把每組的三級拓?fù)鋵哟谓Y(jié)構(gòu)繪制出來,并在拓?fù)渲車L制出必要的簡易文字說明,繪制過程的控制中代碼細節(jié)很多,這里只給出GDI+操作的主要步驟,對于每一組拓?fù)洳捎米陨隙?、從左到右的畫法,如下?/p>
(1)Graphics graphic=panel1.CreateGraphics();即在窗體form中的panel1面板上創(chuàng)建畫板,接下來就可以在panel1上繪制圖形、線條、文字等,但繪制的前提是要根據(jù)panel1的大小以及三級拓?fù)鋵哟谓Y(jié)構(gòu)的大小及位置確定合理的X、Y坐標(biāo),確定繪制對象的長度、粗細、形狀、顏色等屬性。
(2)根據(jù)坐標(biāo)和繪制對象的屬性,利用Pen類實例化畫筆對象,繪制根節(jié)點,形狀為圓形,并利用上述的graphic[5]對象在圓形中繪制一個Image圖形作為根節(jié)點標(biāo)志;再根據(jù)根節(jié)點相對panel1位置繪制出相應(yīng)的線條,用于連接四個代理。
(3)根據(jù)坐標(biāo)和繪制對象屬性,利用Pen類實例化畫筆對象,通過for循環(huán)從左到右繪制出四個矩形,用來作為填充代理圖標(biāo)的框架。這里不需要急于繪制代理圖標(biāo),因為還不知道實際有沒有代理或存在哪些代理。再根據(jù)代理的矩形框圖相對panel1位置繪制出相應(yīng)的線條,用于連接16個測量儀器。
(4)類似步驟3,區(qū)別是for循環(huán)16次,每次繪制正方形,用作填充測量儀器圖標(biāo)的框架。
前面已將三級拓?fù)鋵哟谓Y(jié)構(gòu)框圖繪制出來,接下來需要在其基礎(chǔ)上繪制出實際局域網(wǎng)中設(shè)備的圖標(biāo),這樣就能確定局域網(wǎng)中有哪些設(shè)備,以及這些設(shè)備的連接關(guān)系。前面已提到TreeView控件的局限性,所以將它作為輔助的拓?fù)滹@示,而將文中設(shè)計的拓?fù)溆糜谥饕耐負(fù)滹@示,該主要拓?fù)湟蕾囉谳o助拓?fù)涞耐負(fù)潢P(guān)系及數(shù)據(jù),而該輔助拓?fù)湟蕾囉跀?shù)據(jù)表t_RunningTreeView。所以拓?fù)鋱D的生成[6]步驟大致如下:
(1)生成TreeView。首先從t_RunningTreeView中讀取數(shù)據(jù)存入DataTable內(nèi)存數(shù)據(jù)表中,利用DataTable類的對象的Select方法,即DataRow[] dr=dt.Select("pid="+pid),通過foreach遞歸的從根節(jié)點,即pid為0的節(jié)點開始遍歷dr;每次遍歷實例化新的node對象,用于在TreeView中添加新節(jié)點,即TreeNode node=new TreeNode(),將dr對象的每行值與node的相關(guān)屬性綁定,再把node通過Add方法添加到TreeView;在這個過程中還要進行總代理個數(shù)、總測量儀器個數(shù)等數(shù)據(jù)的統(tǒng)計。
(2)將TreeView中的信息保存到相應(yīng)數(shù)組中,以便將TreeView輔助拓?fù)涞耐負(fù)潢P(guān)系及數(shù)據(jù)與主拓?fù)淇驁D進行數(shù)據(jù)的綁定,即通過foreach語句遞歸地遍歷TreeView中nodes節(jié)點,根據(jù)節(jié)點層次級別等屬性判斷,將node的信息存儲到代理或測量儀器相關(guān)數(shù)組中,并做相關(guān)的數(shù)據(jù)統(tǒng)計,代碼[7]如下:
private void tp_binding(TreeNodeCollection nodes)
{
foreach(TreeNode node in nodes)
{
if(node.Level==1) //代理
{
agentName[i1]=node.Text; //代理名
imgIndexAgent[i1]=node.ImageIndex;//代理圖標(biāo)索引
if(node.Nodes!=null)//該代理下還有孩子節(jié)點,即測量儀器
{
childNodeCount[k1]=node.Nodes.Count;//統(tǒng)計該代理下的儀器數(shù)
k1++;
sub_tp_binding(node.Nodes);//統(tǒng)計測量儀器相關(guān)數(shù)據(jù)
}
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;//測量儀器圖標(biāo)索引
j1++;
n1++;
}
sub_tp_binding(childNode.Nodes);//可以注釋掉,因為沒有四級節(jié)點
}
}
(3)根據(jù)所點擊的某一組,獲取該組索引,利用索引、代理框圖的坐標(biāo)及步驟2得到的節(jié)點信息,通過for循環(huán)依次繪制出四個代理圖標(biāo),并繪制出代理名等文字說明,對于不存在的代理用專門的特定圖標(biāo)替代,不需要繪制代理名等文字說明;代理繪制完成后,統(tǒng)計所查看組的前面所有組代理所連接的測量儀器總數(shù),即:for(int i=0;i<4*index;i++) j1+=childNodeCount[i];這樣就知道了當(dāng)前組中測量儀器圖標(biāo)的起始索引,然后結(jié)合該索引,通過雙重循環(huán)(第一重循環(huán)為每個代理,第二重循環(huán)為每個代理下的四個測量儀器)和判斷,依次繪制每個代理下所連接的測量儀器的圖標(biāo),并繪制出代理名等文字說明,對不存在的測量儀器,用特定圖標(biāo)替代,不需要繪制代理名等文字說明。由于篇幅有限,不具體介紹代碼,最后實現(xiàn)的拓?fù)涫遣榭吹谝唤M拓?fù)滹@示的操作,見圖1(左側(cè)為TreeView目錄樹圖,右側(cè)就是基于TreeView的拓?fù)鋱D)。
圖1 網(wǎng)絡(luò)儀器拓?fù)?/p>
拓?fù)鋱D的刷新實質(zhì)上是清除已有的TreeView目錄樹狀圖和拓?fù)鋱D,再重新生成一次它們,基本原理還是根據(jù)t_RunningTreeView數(shù)據(jù)表中的數(shù)據(jù)生成TreeView目錄樹狀圖,再根據(jù)TreeView節(jié)點的數(shù)據(jù)繪制樹狀拓?fù)鋱D。其缺點是無論拓?fù)鋱D關(guān)系是否發(fā)生變化,就去刷新[8],人為控制時消耗人力,程序輪詢控制時,在時間間隔上又有局限性。要實現(xiàn)動態(tài)刷新,刷新原理不變,區(qū)別是當(dāng)局域網(wǎng)的代理或測量儀器連接狀態(tài)的改變導(dǎo)致拓?fù)鋱D改變時,更新t_RunningTreeView數(shù)據(jù)表,再進行程序控制的刷新。
獲取[9]局域網(wǎng)中拓?fù)鋱D連接狀態(tài)改變的方法有三種,三種方法相互協(xié)調(diào),共同完成各自的任務(wù),缺一不可。由于代碼較多,限于篇幅,這里只概述流程,分別為:
(1)DHCP自動發(fā)現(xiàn)[10]。這是后臺模塊調(diào)用的一個開源DHCP,目的是發(fā)現(xiàn)局域網(wǎng)[11]中的代理開發(fā)板,并為其分配IP地址,然后將IP地址和代理的MAC地址推送給監(jiān)管平臺,推送指的是后臺和監(jiān)管模塊之間采用進程間的socket通信實現(xiàn);監(jiān)管平臺專門創(chuàng)建并啟動了一個后臺線程去接收該推送的任務(wù),接收到推送信息后,將線程將字節(jié)數(shù)組中的數(shù)據(jù)反序列化成對應(yīng)的結(jié)構(gòu)體,并將得到的有效數(shù)據(jù)顯示在監(jiān)管平臺主控界面的運行欄中,還要寫入日志文件,并通過MAC在t_AgentIDMAC數(shù)據(jù)表中查找對應(yīng)代理編號,再根據(jù)IP在t_RunningTreeView表中查找對應(yīng)的ID號,如果沒找到,說明該代理不存在,需要將該代理信息添加至數(shù)據(jù)表t_RunningTreeView中,表明拓?fù)鋱D連接狀態(tài)已改變。反序列化和結(jié)構(gòu)體[12]代碼分別如下:
//對Socket收到的字節(jié)數(shù)組數(shù)據(jù)進行反序列化
public Object BytesToStruct(Byte[] bytes,Type strcutType)
{
int size=Marshal.SizeOf(strcutType);//得到結(jié)構(gòu)體的大小
//分配結(jié)構(gòu)體大小的內(nèi)存空間
IntPtr buffer=Marshal.AllocHGlobal(size);
//從bytes字節(jié)數(shù)組拷貝到內(nèi)存空間
Marshal.Copy(bytes,0,buffer,size);
//將數(shù)據(jù)從非托管內(nèi)存封送到結(jié)構(gòu)類型的托管對象
object obj=Marshal.PtrToStructure(buffer,strcutType);
Marshal.FreeHGlobal(buffer);//釋放非托管內(nèi)存空間
return obj; //返回對象
}
//反序列化后的數(shù)據(jù)結(jié)構(gòu)為結(jié)構(gòu)體,C#中結(jié)構(gòu)的定義如下:
//引用C#的StructLayout特性,說明結(jié)構(gòu)的數(shù)據(jù)字段的物理布局
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=0)]
public struct DHCPInfo
{
//MarshalAs特性表示如何在托管和非托管代碼間封送數(shù)據(jù)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=20)]
public stringMACAddress;//MAC地址字段
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=20)]
public stringIPAddress;//IP字段
}
(2)SNMP協(xié)議的Trap。代理Trap給監(jiān)管平臺的信息包括告警、事件和測量儀器的狀態(tài)等信息,監(jiān)管平臺通過Trap的OID號進行字符串的解析和較為復(fù)雜的過濾、判斷、映射及控制,獲取測量儀器的插拔狀態(tài)信息,更新t_RunningTreeView。Trap消息由代理模塊發(fā)送給后臺模塊,后臺模塊進行一系列處理再通過socket套接字發(fā)送給監(jiān)管模塊。
(3)監(jiān)管平臺的掃描[13]代理線程。代理的上線有DHCP自動發(fā)現(xiàn),測量儀器的插拔有代理的SNMP的Trap。對于代理的斷線,首先它無法通過網(wǎng)絡(luò)Trap,其次這里的DHCP不具備這樣的功能,故只能依賴監(jiān)管平臺每隔一定時間去讀取t_RunningTreeView表中所有代理的ID和IP,然后通過C#中Ping類和PingReply類來Ping每個IP,即發(fā)送ICMP回送請求,并獲取ICMP應(yīng)答狀態(tài),如果應(yīng)答狀態(tài)不成功,則表明該IP對應(yīng)的代理不存在,應(yīng)該從數(shù)據(jù)庫[14]中刪除該代理的記錄并刪除該代理下連接的全部測量儀器,即更新[15]數(shù)據(jù)表t_RunningTreeView,如果Ping通,ICMP應(yīng)答狀態(tài)成功,則讀取下一個ID和IP,然后進行同樣的操作。該掃描關(guān)鍵代碼[16]如下:
Threadt_scan=new Thread(new ThreadStart(scanAgentIp));//創(chuàng)建線程
t_scan.Start();//啟動線程
public voidscanAgentIp()
{
string sqlStr="select ip,id from t_RunningTreeView where pid=1";/* pid為1,表示代理,因為所有代理的父節(jié)點的id是1 */
string connectionString="server=localhost; user id=root; password=lyb; database=xnmsdb";//數(shù)據(jù)庫連接的必要信息
conn_s=new MySqlConnection(connectionString);//實例化數(shù)據(jù)庫連接對象
conn_s.Open(); //打開數(shù)據(jù)庫連接
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); //發(fā)送ICMP回送消息,并得到應(yīng)答
if(reply.Status!=IPStatus.Success)//判斷ICMP應(yīng)答狀態(tài),如果不成功
{
string sqlStr1="delete from t_RunningTreeView where ip='"+ip + "' or pid='" + id + "'";//定義刪除該代理及其所接的測量儀器的命令字符串
MyMysqlMeans.getsqlcom(sqlStr1);/*執(zhí)行數(shù)據(jù)庫操作,調(diào)用監(jiān)管模塊封裝的數(shù)據(jù)庫操作的靜態(tài)類的相應(yīng)靜態(tài)方法 */
load_tp=new Thread(new ThreadStart(createAutoFind));
load_tp.Start();
}
pingSender.Dispose();//銷毀對象
}
read.Dispose();
Thread.Sleep(3000); //隔3 s掃描一次
}
在上述三種方法中更新了t_RunningTreeView數(shù)據(jù)表后,就需要自動重新生成拓?fù)鋱D,即調(diào)用繪制拓?fù)鋱D所封裝的方法[17],然而,這三種方式對應(yīng)三個線程,這三個線程分別產(chǎn)生一個線程去專門執(zhí)行重繪拓?fù)鋱D的方法,如果再次產(chǎn)生的這三個線程都去調(diào)用該方法更新UI界面的拓?fù)鋱D,會產(chǎn)生錯誤。C#中不允許這樣的操作,所以要聲明委托,在線程上執(zhí)行指定的委托。關(guān)鍵代碼如下:
Thread load_tp=new Thread(new ThreadStart(createAutoFind));
load_tp.Start();
public delegate voidmy_autoFind();//聲明委托
public voidcreateAutoFind()
{
this.Invoke(new my_autoFind(autoFindStart));// 在線程上執(zhí)行指定的委托
}
節(jié)點操作事件是對拓?fù)鋱D中的代理和測量儀器進行符合需求的各種操作,即在代理或測量儀器的圖標(biāo)區(qū)域通過單擊鼠標(biāo)右鍵形成各自的菜單欄,再根據(jù)菜單欄選定某一菜單項,完成該菜單項所提供的設(shè)備管理功能。panel1面板鼠標(biāo)單擊事件的注冊代碼如下:
this.panel1.MouseClick+=
newSystem.Windows.Forms.MouseEventHandler(this.panel1_MouseClick);
對于實現(xiàn)panel1_MouseClick(object sender,MouseEventArgs e)方法的代碼細節(jié)較多,這里只給出該方法的算法描述:
首先根據(jù)e對象判斷單擊的是哪個鼠標(biāo)按鈕,如果是左鍵,方法調(diào)用結(jié)束,否則對每一組拓?fù)渲写砗蜏y量儀器所占用的20個區(qū)域,逐個判斷e對象的X、Y坐標(biāo)是否在這些區(qū)域中,如果在(表明就不需要判斷其他區(qū)域了,程序完成了整個功能后可以通過break跳出循環(huán),并結(jié)束),計算出當(dāng)前點擊的是第j索引組拓?fù)鋱D。然后再判斷鼠標(biāo)右擊的第i索引區(qū)域編號是否小于4,如果是,表明該區(qū)域是代理,否則該區(qū)域是測量儀器;其中i取值范圍是-1
如果是代理區(qū)域,即i<4,需用i<(agentNum-4*j)判斷該區(qū)域是否存在代理,如果存在,通過index=i+4*j計算出該代理在整個拓?fù)渲械乃饕?,最后根?jù)該索引在該代理區(qū)域周圍彈出菜單欄,即agentMenu.Show(e.X+250,e.Y+150),程序結(jié)束;其中agentNum表示存在的代理總數(shù)。
如果是測量儀器區(qū)域,要用index=4*j+(i-4)/4計算出該區(qū)域是否屬于index索引的代理下的區(qū)域,需用(index+1)<=agentNum判斷該區(qū)域附屬的索引為index的代理區(qū)域是否存在代理。如果存在,通過node=treeView1.Nodes[0].Nodes[index]獲得TreeView控件中索引為index的代理節(jié)點,如果該代理節(jié)點下還有節(jié)點(測量儀器),通過index1=i%4計算出右擊的測量儀器區(qū)域相對其附屬代理下的4個區(qū)域中的索引號,再通過index1 文中研究了局域網(wǎng)中網(wǎng)絡(luò)儀器拓?fù)鋱D的結(jié)構(gòu)設(shè)計和自動發(fā)現(xiàn)、生成以及節(jié)點的右擊事件,屬于LXI網(wǎng)絡(luò)儀器測控與管理系統(tǒng)項目中監(jiān)管平臺的一部分。該項目具備研究與實現(xiàn)的意義與價值,也具備創(chuàng)新性與實用性;該項目已被驗收,運行正常,達到了預(yù)期目標(biāo)。4 結(jié)束語