SNMP用VC++6.0實現的方法 (轉)

gugu99發表於2007-08-15
SNMP用VC++6.0實現的方法 (轉)[@more@]

  SNMP用VC++6.0實現的方法
  SNMP(Simplework Management Protocol)是簡單管理,主要用來管理,正因為“簡單”,所以其發展很快,到目前為止幾乎所有的網路產品都要為其提供支援,以方便管理員的管理和開發人員的開發。
  在這裡我們不對有關SNMP的概念和基礎知之作過多地闡述,一且假定你對其工作原理有了一定的瞭解,而僅缺的是如何去實現他。
  我們所闡述的是基於VC6.0下的SNMP。詳細介紹一下有關SNMP程式設計的過程及的用法,在遇到難於理解的部分,我會以較大的篇幅作以解釋。言歸正傳,進入我們的主題。
  首先來闡述幾個重要的概念:
  1。community (共同體名):如果翻譯過來可能會顯得難於理解,其實你完全可以把它理解為一個帶有的登陸賬戶,這是你訪問網路裝置的重要憑據,比如你要訪問機,假如交換機的community是public,其許可權是隻讀的,那你一次登陸交換機就可以檢視有關交換機記錄的資料。如果其許可權是讀寫的,你你就有權修改其中的一些設定,如封鎖某一個交換機的埠。大部分交換機預設情況下,以public作為只讀community,以private作為讀寫community。
  2。Oid(標誌符):是以SMI(Structure of Management Information)管理資訊結構為基礎的一系列點分符號,如1.3.6.1.2.1.1.1,這些點分符號在任何網路裝置中都唯一標識某一個資料引數。他們的集合我們稱為MIB(Management Information Base)管理資訊庫。對於他們所標識的意義,讀者可以到網上查詢一下,提供一個簡單的尋找辦法,你可以進入,直接輸入1.3.6.1.2.1.1等點分字串。
  下面進入我們的正題。
 
  和其他程式設計過程一樣,整個SNMP程式設計也要經過一個建立,,銷燬的過程,通俗點說就是要做準備,初始化SNMP環境即載入SNMP的功能,接著就要執行所進行的操作,SNMP是基於訊息機制的,所以訊息傳遞與管理是我們在程式設計中所必須注意的問題,最後要進行銷燬和回收資源,這一點相信程式設計人員
都會注意到。以下我們按步驟給予詳細介紹:
  1。載入SNMP,用到的函式是SnmpStartup(smiLPUINT32 nMajorVersion,
  smiLPUINT32 nMinorVersion,
  smiLPUINT32 nLevel,
  smiLPUINT32 nTranslateMode,
  smiLPUINT32 nRetransmitMode);
  五個引數作為接收引數返回SNMP的主版本號,副版本號,支援最高的操作標準,預設的實體/上下文傳輸,預設的重發機制。

  2。建立會話,用到的函式是HSNMP_SESSION SnmpOpen(
  HWND hWnd,  // handle to the notification window
  UINT wMsg  // window notification message number
  );
  或HSNMP_SESSION SnmpCreateSession(
  HWND hWnd, // handle to the notification window
  UINT wMsg, // window notification message number
  SNMPAPI_CALLBACK pfnCallBack, // notification callback function
  LPVOID lpClientData  // pointer to callback function data
  );
  第二個函式並沒有被完全確定下來,他只是為員在程式設計過程中不是基於的程式設計提供一種選擇,第一個引數指向接收訊息的視窗句炳,第二個引數則指向該視窗需要接收的訊息碼。該函式返回一個會話句炳,這一句炳是在我們以下程式中都要用到的一個重要變數。

  3。設定傳輸模式,用到的函式是SNMPAPI_STATUS SnmpSetTranslateMode(
  smiUINT32 nTranslateMode  // new entity/context translation mode
  );
  該函式只有一個引數,有以下幾種選擇:
  SNMPAPI_TRANSLATED  不常用
  SNMPAPI_UNTRANSLATED_V1  版本V1
  SNMPAPI_UNTRANSLATED_V2  版本V2
  你可以選擇任一個引數,我使用過第二和第三個引數,其區別是在版本一中get_bulk的操作不被允許,因為版本一不支援這種操作,其具體細節可查閱有關資料,推薦一本書《用SNMP管理網際網路絡》。至於第一個引數本人未作過試驗,不能加以妄斷。

  4。建立實體,用到的函式是HSNMP_ENTITY SnmpStrToEntity(
  HSNMP_SESSION session,  // handle to the WinSNMP session
  LPCSTR string  // pointer to a string that ntifies
  // the entity
  );
  該函式的第一個引數是第二步返回的會話句炳,第二個引數與你在第三步中設定的傳輸模式有關,如果你選則後兩個引數,那麼這裡的string就是你要傳送訊息的網路裝置或接收訊息的管理裝置ip地址。根據自己的需要,通常我們將這兩個實體都建立一下。該函式返回一個實體句炳。

  5。設定重傳模式,用到的函式是SNMPAPI_STATUS SnmpSetRetransmitMode(
  smiUINT32 nRetransmitMode  // new retransmission mode
  ); 
  該函式只有一個引數,有以下兩種選擇
  SNMPAPI_ON  啟動重傳模式 
  SNMPAPI_OFF  關閉重傳模式

  6。設定超時時間,用到的函式是SNMPAPI_STATUS SnmpSetTimeout(
  HSNMP_ENTITY hEntity,  // destination management entity
  smiTIMETICKS nPolicyTimeout  // new time-out value for database
  );
 
  該函式的第一個引數是第四步返回的實體句炳,通常我們設定目標實體的超時時間,也就是接收訊息的網路裝置的實體。第二個引數是
超時的時間。

  7。設定重傳次數,用到的函式是SNMPAPI_STATUS SnmpSetRetry(
  HSNMP_ENTITY hEntity,  // destination management entity
  smiUINT32 nPolicyRetry  // new retry count value for database
  );
  該函式的第一個引數是第四步返回的實體句炳,通常我們設定目標實體的重傳次數,也就是接收訊息的網路裝置的實體。第二個引數是
重傳次數。

  8。建立上下文句炳,用到的函式是HSNMP_CONTEXT SnmpStrToContext(
  HSNMP_SESSION session,  // handle to the WinSNMP session
  smiLPCOCTETS string  // pointer to a string structure
  );
  該函式的第一個引數是第二步返回的會話句炳,第二個引數與你在第三步中設定的傳輸模式有關,如果你選則後兩個引數,那麼這裡的string就是共同體名。該函式返回一個上下文句炳。

  由此我們的到了三個重要的句炳,總結一下:1。會話句炳,2。實體句炳,3。上下文句炳,請記住這三個重要的句炳,因為它們在SNMP程式設計過程中時刻用到,只有在結束後才釋放他們。

  9。建立變數捆綁列表,用到的函式是HSNMP_VBL SnmpCreateVbl(
  HSNMP_SESSION session,  // handle to the WinSNMP session
  smiLPCOID name,  // pointer to the variable name
  smiLPCVALUE value  // pointer to the value to associate
  // with the variable
  );
  這是一個比較難理解的函式,要對其有深入的理解,你必須對SNMP的資料包格式有所瞭解,在這裡我不能做過多地闡述,可以給你打個比方,好比一個專列火車,他只負責到目的地接人。開始的時候所有車廂是空的,每個車廂標有號碼,標誌其接的人的特徵,好比第一車廂要接所有姓趙的人,第二節車廂要接局以上的領導,等等。車頭由司機著,他當然知道自己的目的地,一路幾經周折,終於到達目的地,此時車上還未有一個人,目的地的站長首先要檢查該司機的證件和文書,看看他有沒有權利來接走這批人,如果沒有,就會在第一個車廂內坐上一個本站的通知官,他的責任就是返回告訴源站長官說該司機沒有權力接人。如果有,那麼站長要看看各節車廂是否符合本站接人的要求,如果符合,就將不同的人送到不同的車廂就坐。如果有改姓的的請求,那麼要看本站是否允許,如果允許,就將本站這些符合條件的人改姓。
  在這裡我們可以把每節車廂看作是不同變數,車廂的空間是用來接收人的,那麼具有不同特徵的人就是返回資料。這種車廂與其空間就好比一個繫結,SNMP的資料包正如這種方式一次可以有多個繫結。而車廂號就好比物件標誌符。當然只有第一個是特殊的,因為他要有車頭,所以用SnmpCreateVbl函式,其他的繫結我們要用SnmpSetVb,將其依次連線到前一節車廂上。
  好了,由上述,我們可以知道了,該函式的第一個引數是第二步返回的會話句炳,而其他兩個引數開始時就可以置為空了。該函式返回一個繫結列表句炳。

  10。追加繫結列表,用到的函式是SNMPAPI_STATUS SnmpSetVb(
  HSNMP_VBL vbl,  // handle to the variable bindings list
  smiUINT32 index,  // position of the variable binding entry
  // in the list
  smiLPCOID name,  // pointer to the variable name portion
  // of the entry
  smiLPCVALUE value  // pointer to the variable value portion
  // of the entry
  );
  我們可以看到這個函式的後兩個引數與SnmpCreateVbl相同,也就是車廂與空間的繫結。第一個引數是HSNMP_VBL,一個繫結列表句炳,這也可以理解,因為我們已經建立了繫結列表,第二個引數是變數繫結,想一想,這麼多節車廂,總的有個順序吧,別忙,你會提出一個顯而易見的問題,如果我只建立了頭一個繫結列表,我想在向其追加其他的繫結列表,那麼該索引值會有什麼用呢?我完全可以順序加進去啊。就像一個佇列似的。完全正確!實際上該函式也是這樣實現的,當我們要追加變數繫結時,我們須將該索引值置為0。該索引值只是在我們實現諸如set命令時才用到,這將在下文中敘述。
  一且進行得很順利,不過如果你沒有進行過SNMP程式設計的話,你會對其中兩個結構感到迷惑,一個是smiLPCOID,還有就是smiLPCVALUE,別急,先看第一個結構,你會發現有一個英文組合你會很熟悉,因為我在前文介紹過他,就是smiLPCOID中的OID,對了,他的意思是物件標誌符,LP如果你常用VC程式設計也會知道他多數是一個指標了。是了,他就是一個指向smiOID的指標型別。讓我們看看他的內部構造:
  typedef struct {
  smiUINT32  len;  // number of array elements
  smiLPUINT32  ptr;  // pointer to an array of subidentifiers
  } smiOID, *smiLPOID;
  有兩個型別,第一個用來指定他有多少個數字,第二個則指向一個一維陣列,舉個例子,如果一個物件標誌符是1.3.6.1.2.1.1.1,那麼
len應該為8,ptr應該指向一個陣列,該陣列的元素由1,3,6,1,2,1,1,1組成,好像“.”沒有了,正確,因為在SNMP中就要用到這樣的結構,你會想,怎樣才能構造這樣一個結構呢?很簡單,SNMP的API函式給我們提供了方便,你可以定義一個字串如"1.3.6.1.2.1.1.1",用
  SNMPAPI_STATUS SnmpStrToOid(
  LPCSTR string,  // string identifier to convert
  smiLPOID dstOID  // object identifier internal representation
  );函式來進行轉換,看看其中兩個引數,是不是很吻合的對應啊。當然,這種轉換時可逆的,你可以用SnmpOidToStr來進行,具體可以查一下MSDN。
  下面講一講smiLPCVALUE,由上面的推理,你一定可以得出他是一個指向smiVALUE的指標型別。讓我們來看看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, *smiLPVALUE;
  從整體上看,該結構有兩個型別構成,一個是smiUINT32  syntax; 另一個看上去挺複雜,但仔細看卻是一個共同體型別,是了,那麼它們之間有什麼聯絡呢?我們可以想象一下,一個車廂只接收一種型別的資料,該型別的資料又會有他自己的值,如此就好解釋了,syntax就是用來標誌資料型別的,當該型別確定後,我們就從union中找到與它相對應的值value。SNMP中這種設計是很巧妙的。從上面這個結構我們也就瞭解到在SNMP中所用到的基本資料型別了,也就是union中所列出的,在這提一下,通常有關網路裝置描述的值都是smiOCTETS型別。

  再來看看我們已完成的工作,到目前為止我們已經有四個句炳了,1。會話句炳,2。實體句炳,3。上下文句炳,4。繫結列表句炳。還介紹了兩個結構。這兩個結構全和變數繫結列表相關,現在火車的車廂和空間已經有了,車頭也有了,還差給他裝上輪子,找一個好司機了,需要他記住自己的使命。
  11。要想將資料正確的傳送到目的地,我們必須其按照特定的格式來傳送,對於瞭解ip協議的程式設計人員來說,就不需要做過多地解釋了。我們用函式HSNMP_PDU SnmpCreatePdu(
  HSNMP_SESSION session, // handle to the WinSNMP session
  smiINT PDU_type,  // PDU type
  smiINT32 request_id,  // PDU request identifier
  smiINT error_status,  // valid only for SNMP_PDU_GETBULK requests
  smiINT error_index,  // valid only for SNMP_PDU_GETBULK requests
  HSNMP_VBL varbindlist  // handle to the variable bindings list
  );來完成該功能。
  第一個和最後一個引數是我們上面構造的會話句炳和變數繫結列表句炳,第二個引數很重要,他表示我們想要執行的操作方式,SNMP中有如下的選項:
  SNMP_PDU_GET 
  SNMP_PDU_GETNEXT
  SNMP_PDU_RESPONSE
  SNMP_PDU_SET
  SNMP_PDU_V1TRAP
  SNMP_PDU_GETBULK
  SNMP_PDU_TRAP

  對於這些操作,我建議讀者最好找點有關書籍看看,在這裡我只對部分操作大概講解一下,SNMP_PDU_GET通常用來獲得某一個特定的物件標誌符所對應的值,SNMP_PDU_GETNEXT是在程式設計人員不瞭解該表列情況下使用的用來獲取一組值的操作。SNMP_PDU_RESPONSE一般是SNMP填寫的,表示應答發出操作請求的資料包。SNMP_PDU_SET是用來改變某一物件標誌符的值的操作。SNMP_PDU_GETBULK只能在V2版本以上使用,是用來解決SNMP_PDU_GETNEXT一次訊息只能取得一個資料的缺點,可透過發一次訊息取得一組資料。SNMP_PDU_V1TRAP和SNMP_PDU_TRAP使用來發自陷訊息的操作。
  第三個引數request_id,對於同步實現訊息機制的程式設計來說,幾乎沒有作用,但是對於非同步操作,該引數有很重要的作用,你可以用它來標誌某一個請求的訊息,如果有幾個訊息都在訊息佇列中,你可以透過它來確定自己想要處理的訊息,該值完全可以自己來設定。
  error_status和error_index在SNMP_PDU_GETBULK操作中分別為PDU中non_repeaters域定一個值和PDU的max_repetitions域指定一個值。
在其他操作中都為0。
  該函式返回一個PDU句炳。

  萬事俱備了,就讓我們的火車啟航吧。用下面的函式
  12。  SNMPAPI_STATUS SnmpSendMsg(
  HSNMP_SESSION session,  // handle to the WinSNMP session
  HSNMP_ENTITY srcEntity,  // handle to the entity
  HSNMP_ENTITY dstEntity,  // handle to the target entity
  HSNMP_CONTEXT context,  // handle to the context
  HSNMP_PDU PDU  // handle to the PDU
  );
  看看這些引數,是不是我們都已經建立過了,添上他們。

  以上就是整個傳送過程,我們再來理順一下,1。載入SNMP,2。建立會話,3。設定傳輸模式,4。建立實體,5。設定重傳模式,6。設定超時時間,7。設定重傳次數,8。建立上下文句炳,9。建立變數捆綁列表,10。追加繫結列表,11。建立PDU,12。傳送訊息。當然你如果只須獲得一個資料,那麼第10步就不需要了。
 

  接下來我們要接收訊息,並處理他們。
  1。接收訊息,用函式SNMPAPI_STATUS SnmpRecvMsg(
  HSNMP_SESSION session,  // handle to the WinSNMP session
  LPHSNMP_ENTITY srcEntity,  // handle to the source entity
  LPHSNMP_ENTITY dstEntity,  // handle to the target entity
  LPHSNMP_CONTEXT context,  // handle to the context
  LPHSNMP_PDU PDU  // handle to the PDU
  );
  宣告一下,該函式的引數和SnmpSendMsg好像是一樣的,不錯,但引數的進出不一樣,SnmpRecvMsg除第一個引數是我們建立過的以外,其他引數都是輸出引數,就是用來接收的引數,好像很爽的樣子,因為只需自己設定一個引數,其他的宣告一個變數,只管接收就行了。仔細想想,還挺對應的呢!
 
  2。提取資料包,用函式SNMPAPI_STATUS SnmpGetPduData(
  HSNMP_PDU PDU,  // handle to the PDU
  smiLPINT PDU_type,  // PDU_type field of the PDU
  smiLPINT32 request_id,  // request_id field of the PDU
  smiLPINT error_status,  // error_status field of the PDU
  smiLPINT error_index,  // error_index field of the PDU
  LPHSNMP_VBL varbindlist  // handle to the variable bindings list
  );
  也很爽,只有第一個引數是需要你輸入的,而這已經透過SnmpRecvMsg得到了,其他的引數都是需要接收的,看到什麼了,對了,request_id,如果你才用非同步接收的話,它可很重要的啊,可以幫你標識傳送的訊息。還有error_status和error_index,記得嗎,用在SNMP_PDU_GETBULK操作中,他們的意義是不同的,除此之外,他們用來接收SNMP端返回的錯誤訊息,如果返回全都是0,那就是正確返回了,如果不是,那你就的查一查他們所代表的意思了,一般的SNMP書上都會有介紹。

  3。計算返回列表數目,用函式SNMPAPI_STATUS SnmpCountVbl(
  HSNMP_VBL vbl  // handle to the variable bindings list
  );
  將你上一步得到的varbindlist代到裡面去就行了,他的返回只是一個整型,使你所得到的變數繫結列表返回的變數數。

  4。取得返回結果,用函式SNMPAPI_STATUS SnmpGetVb(
  HSNMP_VBL vbl,  // handle to the variable bindings list
  smiUINT32 index,  // position of the variable binding entry
  // in the list
  smiLPOID name,  // pointer to the structure to receive the
  // variable name
  smiLPVALUE value  // pointer to the structure to receive the
  // associated value
  );
  既然在上一步已經得到了結果數,用一個簡單的for迴圈一次將結果取出吧。該函式有四個引數,第一個在第三步已得到,第二個就是你for迴圈中的變數值,記住取得變數是從0開始的,後兩個引數想想是不是與前面某個函式的引數有點相似。對了,前面我們把他們都置為空,現在SNMP代理將返回值添了進去,我們可以坐享其成了,定義兩個變數,接收就行了。提醒一下,對ip地址的接收會有點不同,因為返回值將其封裝為一個指標陣列了,你需要一個一個的取出來!

  看上去工作是做完了,別急,還有一個很重要的環節,難道你沒想過建立了這麼多東西就不會佔用資源嗎?當然要佔用,而且你不釋放他它不會自動釋放,前面我們總共介紹了5個重要的句炳,只有會話句炳是在傳送和接收訊息時都用到的,所以在傳送和接收訊息以後,你要將其他四個句炳釋放掉,那麼會話句炳何時釋放呢?對了,應該在你應用程式退出的過程中釋放掉,進而你會想到建立會話句炳的位置了吧,那就是在建構函式里。以上這些釋放句炳資源的函式SNMP API都有提供,如SnmpFreeEntity,SnmpFreeContext,SnmpFreeVbl,SnmpFreePdu,SnmpClose,他們的引數只有一個,就是你要是放得句炳。最後你要清理整個現場,用函式SnmpCleanup()解決他們吧。
 
  ok了,一且到此完結,大概步驟就這些了,呵呵,你也許對SNMP_PDU_SET和SNMP_PDU_TRAP有些不解,前者你可以在10。追加繫結列表中
改變變數值,當然要遵循smiVALUE的結構,將型別和值都添上,填好需要改變的物件標誌符,11。建立PDU中將型別設為SNMP_PDU_SET就行了。
  traps有些麻煩,因為它的資料包格式有點特別,本人對他也是一知半解,馬馬虎虎能夠實現,待以後整理思路後在與大家交流!
 
  對於想進行SNMP程式設計的人員,在下以菜鳥的身份給你們提個醒,SNMP編成的過程很死,但精心的設計會使你的程式更加的健壯、高效和容易擴充套件,我強烈的建議你們看看hp的snmp++,他的很有層次,極易擴充套件,我的網名本來是zqq_tt但不知被誰申請過了,於是我在csdn裡申請的名字是zqqq1,本人的qq號是81126700。非常願意與大家交流!


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

相關文章