基於WinSNMP的網路管理程式設計----原理與實踐(第三章) (轉)

gugu99發表於2008-03-07
基於WinSNMP的網路管理程式設計----原理與實踐(第三章) (轉)[@more@]

 

第三章 Snmp在下的實現----WinSNMP原理

 

在Windows下實現SNMP的程式設計,可以採用Winsock介面,在161,162埠透過udp傳送資訊。在中,已經封裝了SNMP協議的實現,提供了一套可供在Windows下開發基於SNMP的管理的介面,這就是WinSNMP 。

3.1  什麼是WinSNMP

WinSNMP的目的是為在Windows下開發基於SNMP的網路管程式提供解決方案。它為SNMP網管開發者提供了必須遵循的開放式單一介面規範,它定義了過程、資料型別、資料結構和相關的語法。
圖3.1顯示了一個網路管理站(NMS)和網路管理(Agent)之間端到端的SNMP連線中WinSNMP所處的層次。這是一個WinSNMP的參考模型。


 

圖3.1WinSNMP參考模型


總的來說,WinSNMP以的形式封裝了SNMP協議的各部分(在VC++6.0開發環境中體現為wsn2.dll、wsnmp32.lib和winsnmp.h),且針對SNMP是使用UDP的特點而設定了訊息重傳、超時機制等。

3.2  一些基本概念

在WinSNMP程式設計中,我們需要考慮的基本概念主要有以下幾點:
 SNMP支援層次
 Entity/Context轉換
 本地
 會話
 非同步模式
 管理
下面我們將分別對它們作介紹。

3.2.1  SNMP支援層次(Levels of SNMP Support)
WinSNMP支援四個層次的SNMP操作:
 Level 0 = 只有訊息編碼/解碼
 Level 1 = Level 0 + 與SNMPv1代理的通訊
 Level 2 = Level 1 + 與SNMPv2代理的通訊
 Level 3 = Level 2 + 與其它SNMPv2管理站的通訊
因為SNMP協議支援SNMPv1與SNMPv2的共存,所以WinSNMP實現能提供對兩個版本協議的支援。
SnmpStartup函式能返回當前WinSNMP實現所能提供的最大支援層次。

3.2.2  Entity/Context轉換模式(Entity/Context Translation Modes)
WinSNMP應用程式能夠讓WinSNMP實現把entity和context引數按不同的方式解釋:
(1)按字面解釋為SNMPv1代理的地址和共同體(community)字串。
(2)解釋為SNMPv2的party和context識別符號(context IDs)。
(3)透過查詢本地資料庫將其轉換為各自的SNMPv1或SNMPv2元素。
三種Entity/Context轉換模式如下:
SNMPAPI_TRANSLATED = 透過本地資料庫查詢轉換
SNMPAPI_UNTRANSLATED_V1 = 轉換為地址和共同體(community)字串
SNMPAPI_UNTRANSLATED_V2 = SNMPv2的party和context IDs.
我們可以透過SnmpStartup函式獲得當前預設的entity/context轉換模式,SnmpSetTranslatedMode函式可以用來設定entity/context轉換模式。
當在中採用SNMPv1協議時,我們可以將其設定為SNMPAPI_UNTRANSLATED_V1,具體實現如下:
HSNMP_ENTITY hAgent;
HSNMP_CONTEXT hView;
LPCSTR entityName = “202.120.86.71”;
smiOCTETS contextName;
contextName.ptr = “public”;
contextName.len = lstrlen (contextName.ptr);
hAgent = SnmpStrToEntity (hSomeSessin, entityName);
hView = SnmpStrToContext (hSomeSession, const &contextName);
透過這樣的設定,我們就可以在161埠透過UDP訪問“202.120.86.71”上的SNMP代理了。

3.2.3  本地資料庫(Local Database)
本地資料庫主要重傳模式(RetransmitMode)、重試次數(Retry)、超時(timeout)、轉換模式(TranslateMode)等值。我們可以對其中的資料進行讀(get)、寫(set)操作。

3.2.4  會話(session)
會話是用來管理WinSNMP應用程式和WinSNMP實現之間的連線,由SnmpCreateSession(推薦)或SnmpOpen函式建立。會話是資源管理的最小單位,也是WinSNMP應用程式和WinSNMP實現之間通訊管理的最小單位。一個良好的WinSNMP應用程式應該使用會話結構邏輯地管理它的操作,並將實現中的資源需求控制在最小。
呼叫SnmpCreateSession或SnmpOpen函式建立一個會話時,會返回一個“session id”,這是一個控制程式碼(handle)變數,WinSNMP用它來管理自己的資源。應用程式最終應呼叫SnmpClose函式將會話釋放。

3.2.5  非同步模式(Asynchronous Model)
當代程式設計模式的一個很大特點就是訊息。WinSNMP採用了非同步訊息驅動模式,主要基於兩個原因:
(1) 非同步訊息驅動模式非常適合於面向理論、SNMP分散式管理模型以及Windows程式設計、執行環境。
(2) SNMP再管理站和代理之間傳送資料沒有什麼特別的傳輸機制,它基本上是基於資料包的,沒有在實體之間建立實際通道(虛電路)。這樣的事實使得WinSNMP非常適合採用非同步模式。
現代的訊息驅動程式必須響應各種重要事件,有些則完全依賴於非同步關係。事實上,WinSNMP API中幾乎所有函式都有非同步成分,有些則是完全非同步的。有三個非常重要的非同步函式:
 SnmpSendMsg  (傳送資料)
 SnmpRecvMsg  (接收資料)
 SnmpRegister (註冊接受trap訊息)
WinSNMP的整個程式設計模式就是基於非同步的,我們將在後面做詳細介紹。

3.2.6  記憶體管理(Memory Management)
在Windows程式設計中,記憶體管理一向是一個令人頭疼的問題。在這裡,我們將對WinSNMP的記憶體管理做一個較為詳盡的描述。
WinSNMP包括三種不同的記憶體“物件”:
 控制程式碼式資源 (HANDLE’d Res)
 C風格(以NULL結尾)的字串
 WinSNMP API結構型別

3.2.6.1  控制程式碼式資源 (HANDLE’d Resources)
有五種控制程式碼式資源的變數:
 Sessions
 Entities
 Contexts
 Protocol Data Units (PDUs)
 VarBindLists (VBLs)
所有控制程式碼物件都表示為“HSNMP_<_tag>”的形式,它為WinSNMP實現(以DLL方式)所擁有。

3.2.6.2  C風格字串 (C-Stytle Strings)
C風格的字串主要用來為通用的字串表示與Entity和物件識別符號(OID)物件之間的轉換提供便利。WinSNMP中使用C風格字串的函式有:SnmpStrToEntity、SnmpEntityToStr、SnmpStrToOid、SnmpOidToStr。
C風格字串的記憶體分配、管理和釋放完全由應用程式負責。因此我們還需要傳遞“size”引數給使用它的函式。

3.2.6.3  描述符 (Descriptors)
WinSNMP中有三種結構型別:
 smiOCTETS
 smiOID
 smiVALUE
前兩種型別的定義如下:
typedef struct {
  smiUINT32 len;  /*unsigned long integer 型別,表示ptr中的位元組數*/
  smiLPBYTE ptr;  /*指向包含octet string的位元組陣列的far指標*/
} smiOCTETS;

typedef struct {
smiUINT32  len;  /**unsigned long integer 型別,表示ptr中無符號長整形的個數*/
  smiLPUINT32 ptr;  /*指向由OID各個識別符號組成的無符號長整形數祖的far指標*/
} smiOID;

smiVALUE稍微複雜一點,它的定義如下:
typedef struct { /* smiVALUE portion of VarBind */
 smiUINT32 syntax; /* Insert SNMP_SYNTAX_ */
 union {
 smiINT sNumber; /* SNMP_SYNTAX_INT
  SNMP_SYNTAX_INT32 */
 smiUINT32 uNumber; /* SNMP_SYNTAX_UINT32
  SNMP_SYNTAX_CNTR32  SNMP_SYNTAX_GAUGE32  SNMP_SYNTAX_TIMETICKS */
 smiCNTR64 hNumber; /* SNMP_SYNTAX_CNTR64 */
 smiOCTETS string; /* SNMP_SYNTAX_OCTETS
  SNMP_SYNTAX_BITS
  SNMP_SYNTAX_OPAQUE
  SNMP_SYNTAX_IPADDR
  SNMP_SYNTAX_NSAPADDR */
 smiOID oid; /* SNMP_SYNTAX_OID */
 smiBYTE empty; /* SNMP_SYNTAX_NULL
  SNMP_SYNTAX_NOSUCHOBJECT
  SNMP_SYNTAX_NOSUCHINSTANCE
  SNMP_SYNTAX_ENDOFMIBVIEW */
  } value; /* union */
 } smiVALUE;

當一個應用程式得到一個smiVALUE變數時,首先必須檢查它的“syntax”成員,已決定怎樣取到它的第二個成員。
當“syntax”成員變數顯示“value”值是一個smiOCTETS或smiOID物件時,我們就應該考慮記憶體管理,約定如下:
(1)  當其作為輸入引數時,應用程式負責為變長物件分配記憶體;
(2)  當其作為輸出引數時,由WinSNMP實現(表現為DLL)為變長物件分配
記憶體。 

3.2.6.4  記憶體的釋放
WinSNMP應用程式必須負責釋放所有透過呼叫WinSNMP API函式所分配的資源,主要有以下三類函式:
 SnmpFree: 釋放Entity、Context、Pdu、Vbl、Descriptor
 SnmpClose  : 關閉會話
 SnmpCleanup  : 必須在程式結束之前呼叫,釋放所有資源
應用程式推薦使用上述的順序來釋放所有的WinSNMP資源。

3.3  WinSNMP基本程式設計模式

WinSNMP API按照SNMP協議封裝了各種操作,包括PDU、VarBindList以及協議操作的各項函式。我們可以按照SNMP協議的描述,呼叫WinSNMP相關函式,完成一次完整的SNMP。我們下面將以筆者完整的系統(採用SNMPv1協議)為例,具體描述WinSNMP的一般程式設計模式。我們分傳送請求訊息與接受響應訊息兩部分來實現。

3.3.1  WinSNMP傳送請求訊息
WinSNMP傳送請求訊息的過程可以分為四個部分,主要有:WinSNMP的初始化、PDUs的建立、傳送資訊以及資源的釋放。

3.3.1.1  WinSNMP的初始化
(1) 呼叫SnmpStartup函式啟動WinSNMP。
(2) 呼叫SnmpCreateSession函式建立一個會話session。
(3) 呼叫SnmpSetRetransmitMode函式設定重傳模式。
(4) 呼叫SnmpSetRetry函式設定重傳次數。
(5) 呼叫SnmpSetTimeout函式設定超時時間。
其中第3、4、5步都是對本地資料庫的操作,完成了對WinSNMP相關引數的設定。

3.3.1.2  建立協議資料單元(PDUs)
在建立PDU之前,我們必須先建立變數繫結表(varbindlists)。
(1) 呼叫SnmpStrToOid函式建立讀取物件的OID,例如,我們建立MIB變數ipInReceives(一個例項的OID為1.3.6.1.2.1.4.3.0),我們可以採用下面的程式碼:
LPCSTR name="1.3.6.1.2.1.4.3.0";
smiOID Oid;
SnmpStrToOid(name,&Oid);

(2) 呼叫SnmpCreateVbl函式建立變數繫結表。
HSNMP_VBL m_hvbl=SnmpCreateVbl(session,&Oid,NULL);/*NULL表示該OID的值為空*/
(3) 呼叫SnmpSetVb函式往變數繫結表中新增變數繫結,我們需先創
建一個OID,命名為Oid。
SnmpSetVb(m_hvbl,0,&Oid,NULL);/*0表示往變數繫結表中新增變數繫結,非0值表示修改此位置的變數繫結*/
建立好了變數繫結表後,我們呼叫SnmpCreatePdu函式建立協議資料單元,在這個函式中,我們必須設定error_index、error_status、request_id引數,它們都與協議中相應的量對應。
HSNMP_PDU m_hpdu=SnmpCreatePdu(session,SNMP_PDU_GET,
NULL,NULL,NULL,m_hvbl);

3.3.1.3  傳送資訊
我們首先呼叫SnmpStrToContext和SnmpStrToEntity函式建立共同體(community)字串和代理entity,具體實現見3.2.2。
然後,我們呼叫SnmpSendMsg函式傳送資訊。
SnmpSendMsg(session,NULL,hAgent,hView,m_hpdu);

3.3.1.4  資源的釋放
最後,我們應該釋放所有分配的資源。

3.3.2  WinSNMP接受響應訊息
還記得前面的SnmpCreateSession函式嗎?它可以說是WinSNMP非同步訊息驅動模式的一個關鍵,讓我們先來看看它的函式原型:
HSNMP_SESSION SnmpCreateSession(
  HWND hWnd,  // handle to the notification window
  UINT wMsg,  // window notification message number
  SNMPAPI_CALLBACK fCallback,  // notification callback function
  LPVOID lpClientData  // pointer to callback function data
);
它提供了兩種方式的非同步訊息驅動,我們可以讓WinSNMP在有響應訊息到達時傳送一個訊息給系統,也可以讓它自動呼叫一個函式。筆者採用了第一種方式,實現如下:
session=SnmpCreateSession(m_hWnd,wMsg,NULL,NULL);
我們可以給訊息wMsg建立一個訊息處理函式,在這個函式里處理訊息的接收、資訊的提取與處理等事務。
下面我們將具體描述WinSNMP接受響應訊息的步驟。
(1) 呼叫SnmpRecvMsg函式接收資料
(2) 呼叫SnmpGetPduData函式從PDU中析取出資料,
(3) 呼叫SnmpCountVbl獲得變數繫結列表中變數繫結的個數
(4) 呼叫SnmpGetVb函式取得PDU變數繫結表中每個變數繫結的OID及其對應的值,可以指明該變數繫結在變數繫結表中的位置。參考實現如下:
int nCount=SnmpCountVbl(varbindlist);
for(int index=1;i<=nCount;i++)
  SnmpGetVb(varbindlist,index,&Oid,value[i]);
其中,index指定了變數繫結的位置,value[i]表示接收到的OID變數的值,是smiLPVALUE型別的,Oid表示接收到的變數繫結的OID。
對於value[i],我們可以參考3.2.6.3節,按照它的syntax成員,用 case語句,分別轉換為字串或整數型別。
(5) 呼叫SnmpOidToStr函式將Oid轉換為字串。並將接收到的Oid與傳送資料包的各OID做比較,已決定各自值的歸屬。引用一段程式碼
if(strcmp(m_sOid[i],m_initOid[1])==0)
 m_sDesr= str[i];
 else if (strcmp(m_sOid[i],m_initOid[2])==0)
 m_sSysOid=str[i];
 else if (strcmp(m_sOid[i],m_initOid[3])==0)
 m_sSysTime=str[i];
 else if (strcmp(m_sOid[i],m_initOid[4])==0)
 m_sName=str[i];
 else if (strcmp(m_sOid[i],m_initOid[5])==0)
 {m_sIpin=str[i];
 m_nIpin=nIpin;}
 else if(strcmp(m_sOid[i],m_initOid[6])==0)
 m_sIpout=str[i];
當我們比較傳送的OID與接收到的OID時,我們就知道了這個str[i]是屬於哪個OID的值,應當放在哪裡顯示,以m_s開頭的變數都代表了不同的label,這樣,相應的值就在相應的字串中顯示。

透過這樣的步驟,我們就完成了一個簡單的SNMP網路管理程式的設計。但是,在具體的應用中,我們應該考慮更多的問題,如記憶體管理、錯誤處理等問題,還有很多問題需要我們在系統開發的過程中去發現、解決。下面,我將描述幾個我在系統開發中遇到的問題,有的已經解決,有的還在探索中,希望能為同仁提供參考。

3.4  幾個問題

3.4.1 讀IP地址
前面講到,IpAddress是SMIv1的一個應用資料型別,表示IP地址,它的定義為:
IpAddress::=[APPLICATION 0] IMPLICIT OCTET STRING(SIZE(4))
當我們讀取一個表示IP地址的OID時,我們應該分別讀出IpAddress四個位元組的值,再將它們處理成我們平時見到的IP地址的形式。程式碼如下:
case SNMP_SYNTAX_IPADDR:
strIp.Format("%d",*m_value[i]->value.string.ptr);
 strIp+=".";
 strTemp.Format("%d",*(m_value[i]->value.string.ptr+1));
 strIp+=strTemp;
 strIp+=".";
 strTemp.Format("%d",*(m_value[i]->value.string.ptr+2));
 strIp+=strTemp;
 strIp+=".";
 strTemp.Format("%d",*(m_value[i]->value.string.ptr+3));
 strIp+=strTemp;

3.4.2  GETNEXT操作的實現
GETNEXT是SNMP中用來讀取表格變數的一個操作。在WinSNMP中,我們可以透過SnmpCreatePdu(session,SNMP_PDU_GETNEXT,NULL,NULL,NULL,m_hvbl)來建立一個GETNEXT操作的PDU。
關鍵的問題是我們如何對這個表格作遍歷。(1).如何判斷表格的結束;(2).在接收到響應訊息時如何處理。
我們下面將以筆者系統為例,說明這些問題。我們將獲得本機的表的一部分。先構造一個函式,程式碼如下:
void CSnmpManagerDlg::Next(LPTSTR Oid)
{
 CString str(Oid);
 if(!strcmp(str.Left(20),"1.3.6.1.2.1.4.21.1.7"))
 {
 理接收到的資料
 pSnmp.CreateVbl(Oid,NULL);
 pSnmp.CreatePdu(SNMP_PDU_GETNEXT,NULL,NULL,NULL);
 pSnmp.Send("127.0.0.1","public");
 }
 else
 {
 m_bNext=FALSE;
 去顯示
 }
}
我們把接收到的OID的前20位與路由next hop MIB變數("1.3.6.1.2.1.4.21.1.7")作比較,假如不等,就說明這一列已經結束。把資料送去顯示或進一步處理。
我們可以為這一操作建立一個新的會話(session),或繼續使用前面GET操作的會話。建立一個新的會話時,我們為這個會話指定一個訊息處理函式,並在這個函式中,處理接收到的資料,以及呼叫Next(LPTSTR Oid)函式繼續傳送GETNEXT操作。
假如繼續使用以前的會話,我們要依靠標誌m_bNext,判斷m_bNext的真假以決定是否繼續發GETNEXT資料包。
void CSnmpManagerDlg::OnRecv()
{ 收、處理訊息
if(m_bNext==TRUE)
 Next(m_sOid);
}
這樣,我們就完成了對錶格中一列的遍歷。同樣,我們可以完成對整個表格的遍歷,我們只需strcmp(str.Left(18),"1.3.6.1.2.1.4.21.1"),就可以獲得整個表格的結束。再在Next(LPTSTR Oid)函式中用switch-case語句按各個MIB變數的值分類,就可以得到整個表格。

3.4.3  對錶格變數的SET操作
在整個系統的開發中,我們曾經對SysName變數進行SET操作。證明是可行的。但當我們SET一個表格變數時,報告變數繫結(VB)錯誤,型別為bad value。可能有兩個原因。
(1). 代理程式(Agent)不支援對這些表格變數的SET操作。(具體見1212)
(2). 當SET一個表格變數時,我們應該對錶格中的所有變數都賦值,並封裝成一個PDU發出去。因為當我們用route add新增路由表時,必須指定所有的引數。並且,表格變數只允許新增與刪除兩種操作。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1000537/,如需轉載,請註明出處,否則將追究法律責任。

相關文章