在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的特色而設置了消息重傳、超時機制等。session
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;
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_<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函數設置超時時間。
其中第三、四、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;
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"))
{
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添加路由表時,必須指定全部的參數。而且,表格變量只容許添加與刪除兩種操做。