天天看點

Snmp在Windows下的實作----WinSNMP程式設計原理

在Windows 下實作SNMP協定的程式設計,可以采用Winsock接口,在161,162端口通過udp傳送資訊。在Windows 2000中,Microsoft已經封裝了SNMP協定的實作,提供了一套可供在Windows下開發基于SNMP的網絡管理程式的接口,這就是 WinSNMP API。

3.1 什麼是WinSNMP

WinSNMP的目的是為在Windows下開發基于SNMP的網絡管程式提供解決方案。它為SNMP網管開發者提供了必須遵循的開放式單一接口規範,它定義了過程調用、資料類型、資料結構和相關的文法。

圖3.1顯示了一個網絡管理站(NMS)和網絡管理代理(Agent)之間端到端的SNMP連接配接中WinSNMP所處的層次。這是一個WinSNMP的參考模型。

圖3.1WinSNMP參考模型

總的來說,WinSNMP以函數的形式封裝了SNMP協定的各部分(在VC++6.0開發環境中展現為wsnmp32.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通路IP位址“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 Resources)

 C風格(以NULL結尾)的字元串

 WinSNMP API結構類型

3.2.6.1 句柄式資源 (HANDLE’d Resources)

有五種句柄式資源的變量:

 Sessions

 Entities

 Contexts

 Protocol Data Units (PDUs)

 VarBindLists (VBLs)

所有句柄對象都表示為“HSNMP_<object_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;

smiUINT32   len; /**unsigned long integer 類型,表示ptr中無符号長整形的個數*/

     smiLPUINT32 ptr;   /*指向由OID各個辨別符組成的無符号長整形數祖的far指針*/

} smiOID;

smiVALUE稍微複雜一點,它的定義如下:

typedef struct {     /* smiVALUE portion of VarBind */

smiUINT32 syntax;   /* Insert SNMP_SYNTAX_<type> */

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<xxx>: 釋放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成員,用select 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;

   strTemp.Format("%d",*(m_value[i]->value.string.ptr+2));

   strTemp.Format("%d",*(m_value[i]->value.string.ptr+3));

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"))

file://處

理接收到的資料

   pSnmp.CreateVbl(Oid,NULL);

   pSnmp.CreatePdu(SNMP_PDU_GETNEXT,NULL,NULL,NULL);

   pSnmp.Send("127.0.0.1","public");

}

else 

   m_bNext=FALSE;

file://送

去顯示

我們把接收到的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()

file://接

收、處理消息

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操作。(具體見RFC1212)

(2). 當SET一個表格變量時,我們應該對表格中的所有變量都指派,并封裝成一個PDU發出去。因為當我們用route add添加路由表時,必須指定所有的參數。并且,表格變量隻允許添加與删除兩種操作。