在大中型企業信息系統中,對客戶端PC的管理,往往是容易出現問題的環節。因此,很多大公司引入了各種分布式的管理系統,例如防病毒方面的Norton AntiVirus,BlackICE防火墻,微軟的 SMS (System Management Server),等等,這些系統都會在客戶端安裝相應的客戶端軟件,一般都是以服務的形式出現,但是由于種種原因,這些服務會停止運行或者該客戶機根本沒有安裝這些客戶端服務,這樣管理系統就會出現疏漏,有可能造成問題,如因無法防御病毒而成為病毒源,無法為該客戶端發布軟件,無法管理客戶PC等等。在此,我們提供一個方案,可以定時按照IP地址掃描網絡,報告出特定的服務的狀態。
這個方案使用了Microsoft.NET技術,同時也用到了.NET Framework中的ADO.NET ,WMI management,XML。其核心是一個由VB.NET寫的程序以及它的兩個配置文件,配置文件為XML格式,該程序按IP掃描網絡,得到每個系統的服務 的狀態,如果IP地址沒有對應系統,則忽略該IP,針對沒有安裝服務或服務停止的系統我們在另一個線程中運行NBTSTAT命令,得到其機器名,用戶名,MAC地址域等信息,以便我們找到機器解決問題。其次為了保存掃描的結果,我們需要一個很小的數據庫MS-Access或MS-SQL server都可以,本文使用SQL2000 。最后為了呈現出掃描的結果,以便我們采取行動,這里我們使用網頁的形式把數據庫中的結果展現出來。
1. VB.NET程序
該程序使用兩個XML格式的配置文件,當程序啟動時會讀入這些配置。其中一個文件定義了需要掃描的網段,包括排除在外的地址段。另一個文件定義了連接數據庫的信息,以及數據表的定義。這兩個文件的內容如下:
<IPLIST>
<IP LANID="192.168.100." ><EXP L=”1” H=”30”/></IP>
<IP LANID="192.168.101." />
<IP LANID="192.168.102." />
<IP LANID="192.168.103." />
<IP LANID="192.168.104." ><EXP L=”1” H=”40”/></IP>
</IPLIST>
該文件定義將要掃描5個網段,其中兩個網段有些地址需要排除在外(分配給打印機等設備),對于192.168.100段,我們排除從1到30,對于192.168.104段我們排除1到40。
<DBINFO>
<SERVER>DBServer</SERVER>
<DATABASE>DB</DATABASE>
<UID>REPORT</UID>
<PWD>REPORT</PWD>
<SERVICE TABLE=”SERVICE”>SERVICE</SERVICE>
</DBINFO>
該文件定義了連接數據庫所需的信息
TAG Meaning <SERVER> SCANSERVICE數據庫的服務器名 <DATABASE> SCANSERVICE數據庫名 <UID> 用于更新SCANSERVICE數據庫的數據庫用戶名 <PWD> 用于更新SCANSERVICE數據庫的數據庫用戶的密碼 <SERVICE> 該TAG的 inner 定義了我們希望掃描的Service的名字, 這里我們假定希望掃描服務名為SERVICE。 該TAG的屬性定義了數據庫中表名,該表用于保存掃描結果。
‘首先我們定義一個類,主要用于得到某個IP地址的Service的狀態信息,并在服務狀態不正常時觸發另一線程得到該系統的詳細信息。
Imports System.ServiceProcess
Imports System.Xml
Imports System.Threading
Public Class GetStatus
Private IServiceName As String ‘服務的名稱
Private IMachineIP As String ‘IP地址
Private ITable As String ‘在DATESET中的表名
‘構造函數
Sub New(ByVal Ip As String, ByVal SvcName As String, ByVal updatetable As String)
IMachineIP = Ip
IServiceName = SvcName
ITable = updatetable
End Sub
‘每個線程所運行的方法,用于得到服務的狀態,如果狀態不正常則觸發另一線程得到該IP的信息
Sub GetStausF()
Dim ServiceP As New ServiceController() ‘實例化一個ServiceController類
ServiceP.MachineName = IMachineIP
ServiceP.ServiceName = IServiceName
Dim myRow As DataRow
Dim status As String
Dim Run As Boolean = False
myRow = ds.Tables(ITable).NewRow
Try
If ServiceP.Status.ToString <> "Running" Then
status = ServiceP.Status.ToString‘如果狀態不是RUNNING則將狀態賦予字符串變量
Else
Run = True ‘如果狀態為RUNNING,則不做任何事
End If
Catch er As Exception ‘以下處理取得狀態時候發生的異常
status = Left(er.Message, 35)
If InStr(status, "Service Control Manager") = 0 Then
status = "Not installed or open service failed" ‘沒有安裝該服務
ElseIf InStr(er.Message, "Manager") > 0 Then
status = "Can not detected" ‘服務的狀態不可得
End If
End Try
ServiceP.Close() ‘關閉ServiceController實例
‘以下判斷如果狀態不是RUNNING,則記錄該系統,并觸發線程得到它的詳細信息。
If Not Run Then
myRow("msg") = status
myRow("ip") = IMachineIP
SyncLock GetType(AddRow) ‘為保證多線程情況下,對DataSet只有一個寫操作,鎖定AddRow類
Dim AddRowIns As New AddRow(myRow) ‘將IP和狀態通過我們自己寫的AddRow類插入DataSet
End SyncLock
‘觸發另一線程取得機器信息
Dim HostInfo2 As New HostInfo(IMachineIP)
Dim HostThr2 As New Thread(New ThreadStart(AddressOf HostInfo2.sysInfo))
HostThr2.Start()
SyncLock GetType(HostInfoThreadCounter)
HostInfoThreadCounter.counter += 1 ‘啟動線程數加1
End SyncLock
End If
SyncLock GetType(StoppCounter)
StopThr.AddStop()
End SyncLock
End Sub
End Class
‘該類只有一個方法,就是將停止的線程數減1
Class StoppCounter
Sub AddStop()
ThreadCounterStopped = ThreadCounterStopped + 1
End Sub
End Class
‘此類用于將已有的行插入DataSet
Class AddRow
‘第一個構造函數,以構造好的行為輸入參數
Sub New(ByVal row As DataRow)
Try
ds.Tables(0).Rows.Add(row)
Catch ee As Exception
End Try
End Sub
‘第二個構造函數,以機器名用戶名等字符串為參數,更新已有的行
Sub New(ByVal IP As String, ByVal user As String, ByVal hostname As String, ByVal Mac As String, ByVal domain As String, ByVal timeout As Char)
Dim RowTimeOut As DataRow
Try
For Each RowTimeOut In ds.Tables(0).Select("IP='" & IP & "'")
RowTimeOut.Item("LastUID") = user
RowTimeOut.Item("Name") = hostname
RowTimeOut.Item("Mac") = Mac
RowTimeOut.Item("Domain") = domain
RowTimeOut.Item("Timeout") = timeout 'Set timeout flag to this item
Exit For 'just run once
Next
Catch er As Exception
End Try
End Sub
End Class
‘由于篇幅限制,這里省略了根據IP取得機器信息的類的代碼。
Imports System.Threading ‘用于支持多線程
Imports System.Xml ‘用于分析XML格式的參數文件
Imports System.Data ‘用于保存結果到數據庫
Module Module1
Public ds As New DataSet()
Public conn1 As SqlClient.SqlConnection ‘數據庫連接
Public ipf As String ‘IP列表文件名
Public dbf As String ‘數據庫信息文件
Public ThreadCounterStopped As Integer
Public StopThr As New StoppCounter()
Sub Main() ‘程序主程序
Dim machineIP As String
Dim iplistF As New Xml.XmlDocument()
Dim iplist As Xml.XmlNode
Dim ipitem As Xml.XmlNode
Dim DBinfoF As New Xml.XmlDocument()
Dim DBinfo As Xml.XmlNode
Dim LanID As String
Dim i As Integer
Dim timestart As Integer
Dim ThreadCounterStarted As Integer
ThreadCounterStarted = 0
ThreadCounterStopped = 0
Dim server As String
Dim database As String
Dim uid As String
Dim pwd As String
Dim table As String
Dim connstr, connstr1 As String
Dim ServiceName As String
Dim Purgestr As String
Try
DBinfoF.Load(dbf) ‘讀取數據庫信息文件
Catch nodb As Exception
MsgBox(nodb.Message & "Wrong DB info file name.")
Exit Sub
End Try
Try
iplistF.Load(ipf) ‘讀取IP列表文件
Catch noip As Exception
MsgBox(noip.Message & "Wrong IP list file name.")
Exit Sub
End Try
‘分析數據庫信息文件
DBinfo = DBinfoF.ChildNodes(0)
server = DBinfo.ChildNodes(0).InnerText
database = DBinfo.ChildNodes(1).InnerText
uid = DBinfo.ChildNodes(2).InnerText
pwd = DBinfo.ChildNodes(3).InnerText
ServiceName = DBinfo.ChildNodes(4).InnerText
table = DBinfo.ChildNodes(4).Attributes(0).Value
‘根據分析所得,構造連接字符串
connstr1 = "server=" & server & ";database=" & database & ";uid=" & uid & ";password=" & pwd
conn1 = New SqlClient.SqlConnection(connstr1) ‘實例化數據庫連接
conn1.Open() ‘打開數據庫連接
Dim sa As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter("select * from " & table, conn1)
Dim combu As New SqlClient.SqlCommandBuilder(sa)
sa.Fill(ds, table) ‘填充DataSet
ds.Clear() ‘清空舊的數據
Dim IPAddress As String
‘分析IP列表文件
iplist = iplistF.ChildNodes(0)
Dim Ai As Integer
Dim ipexcepCount As Integer
Dim ipexcep As Xml.XmlNode
For Each ipitem In iplist.ChildNodes
Dim Excep(2, 83) As Integer
LanID = ipitem.Attributes(0).Value‘得到網絡ID
For i = 2 To 254 ‘從2到254,根據每個網絡ID構造IP地址
Ai = 0
‘以下判斷是為了跳過保留地址段
If ipitem.HasChildNodes Then
ipexcepCount = ipitem.ChildNodes.Count
ReDim Excep(2, ipexcepCount - 1)
For Each ipexcep In ipitem.ChildNodes
Excep(0, Ai) = CInt(ipexcep.Attributes(0).Value)
Excep(1, Ai) = CInt(ipexcep.Attributes(1).Value)
Ai = Ai + 1
Next
End If
For Ai = 0 To ipexcepCount - 1
If i >= Excep(0, Ai) And i <= Excep(1, Ai) Then
Console.WriteLine("跳過保留地址: " & LanID & i.ToString)
GoTo SkipIP
End If
Next
machineIP = LanID & i.ToString ‘IP地址
‘以下觸發線程以,得到服務狀態
Dim getSt As New GetStatus(machineIP, ServiceName, table)
Dim GetStThread As New Thread(New ThreadStart(AddressOf getSt.GetStausF))
GetStThread.Start()
ThreadCounterStarted = ThreadCounterStarted + 1‘啟動線程數加1
Console.WriteLine("線程" & machineIP & " 啟動。檢測 " & ServiceName)
‘每啟動100個線程,程序主線程停止15秒,避免太多線程造成內存溢出
If (ThreadCounterStarted Mod 100) = 0 Then
Console.WriteLine("等待 .......")
Thread.CurrentThread.Sleep(15000)
GC.Collect() 'force garbage collection to aviod outOfMemory when run with long IP list
End If
SkipIP:
Next
Next
Console.WriteLine("Exiting program ...") ‘所有線程都已觸發
Finish:
Thread.CurrentThread.Sleep(5000) ‘以下程序等待所有線程結束
GC.Collect()
If ThreadCounterStopped = ThreadCounterStarted And HostInfoThreadCounter.counter = HostInfoThreadCounter.counterSTOP Then‘如果觸發線程等于結束線程
Dim row As Data.DataRow
For Each row In ds.Tables(table).Rows
row.Item("SysTime") = Now
Next
Purgestr = "delete " & table
Dim com1 As New SqlClient.SqlCommand(Purgestr, conn1)
com1.ExecuteNonQuery() ‘刪除舊記錄
sa.InsertCommand = combu.GetInsertCommand
sa.Update(ds, table) ' 將新記錄寫入數據庫
Else
GoTo Finish ' goto finish and wait another 30 seconds
End If
End Sub
可以利用如下命令在DOS窗口啟動該程序。
Scanservice –i iplist.xml –d dbinfo.xml
2. SCANSERVICE 數據庫
該數據庫保存保存程序運行結果,以便用WEB等方式展現出來。以下是建立表的腳本,包含域名,用戶名,機器名,IP以及服務狀態。
CREATE TABLE [dbo].[Service] (
[IP] [varchar] (50) NULL ,
[狀態] [varchar] (50) NULL ,
[用戶名] [varchar] (50) NULL ,
[機器名] [varchar] (50) NULL ,
[MAC地址] [varchar] (50) NULL ,
[域] [varchar] (50) NULL ,
[超時] [varchar] (10) NULL ,
[時間安] [DateTime] (8) NULL ,
)
總結:
以上是一個完整的方法,也是比較簡單明晰的解決方法,如果要求技巧和性能的話,還有一些地方可以做些改進,比如對線程池的使用。另外還有一些方面需要大家自己完成,比如將數據庫中的信息以WEB的方式展現出來。
|