winsnmp learn (2) winsnmp编程基本流程

       在 Windows 下实现 SNMP 协议的编程,可以采用 Winsock 接口,在 161 162 端口通过 udp 传送信息。在 Windows 2000 中, Microsoft 已经封装了 SNMP 协议的实现 , 提供了一套可供在 Windows 下开发基于 SNMP 的网络管理程序的接口,这就是 WinSNMP API


什么是WinSNMP

WinSNMP的目的是为在Windows下开发基于SNMP的网络管程序提供解决方案。它为SNMP网管开发者提供了必须遵循的开放式单一接口规范,它定义了过程调用、数据类型、数据结构和相关的语法。

     总的来说, WinSNMP 以函数的形式封装了 SNMP 协议的各部分(在 VC++6.0 开发环境中体现为 wsnmp32.dll wsnmp32.lib winsnmp.h ),且针对 SNMP 是使用 UDP 的特点而设置了消息重传、超时机制等。


WinSNMP编程中,我们需要考虑的基本概念主要由以下几点:

  • SNMP支持层次

  • Entity/Context转换模式

  • 本地数据库

  • 会话

  • 异步模式

  • 内存管理

下面我们将分别对它们作介绍。

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协议支持SNMPv1SNMPv2的共存,所以WinSNMP实现能提供对两个版本协议的支持。

SnmpStartup函数能返回当前WinSNMP实现所能提供的最大支持层次。


 Entity/Context转换模式(Entity/Context Translation Modes

WinSNMP应用程序能够让WinSNMP实现把entitycontext参数按不同的方式解释:

1)按字面解释为SNMPv1代理的地址和共同体(community)字符串。

2)解释为SNMPv2partycontext标识符(context IDs)。

3)通过查询本地数据库将其转换为各自的SNMPv1SNMPv2元素。

三种Entity/Context转换模式如下:

SNMPAPI_TRANSLATED = 通过本地数据库查询转换

SNMPAPI_UNTRANSLATED_V1 = 转换为地址和共同体(community)字符串

SNMPAPI_UNTRANSLATED_V2 = SNMPv2partycontext 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代理了。

本地数据库(Local Database

本地数据库主要存储重传模式(RetransmitMode)、重试次数(Retry)、超时(timeout)、转换模式(TranslateMode)等值。我们可以对其中的数据进行读(get)、写(set)操作。


会话(session

会话是用来管理WinSNMP应用程序和WinSNMP实现之间的连接,由SnmpCreateSession(推荐)SnmpOpen函数创建。会话是资源管理的最小单位,也是WinSNMP应用程序和WinSNMP实现之间通信管理的最小单位。一个良好的WinSNMP应用程序应该使用会话结构逻辑地管理它的操作,并将实现中的资源需求控制在最小。

调用SnmpCreateSessionSnmpOpen函数创建一个会话时,会返回一个“session id”,这是一个句柄(handle)变量,WinSNMP用它来管理自己的资源。应用程序最终应调用SnmpClose函数将会话释放。


异步模式(Asynchronous Model

当代编程模式的一个很大特点就是消息驱动。WinSNMP采用了异步消息驱动模式,主要基于两个原因:

(1) 异步消息驱动模式非常适合于面向对象理论、SNMP分布式管理模型以及Windows编程、运行环境。

(2) SNMP再管理站和代理之间传送数据没有什么特别的传输机制,它基本上是基于数据报的,没有在远程实体之间建立实际通道(虚电路)。这样的事实使得WinSNMP非常适合采用异步模式。

现代的消息驱动程序必须响应各种重要事件,有些则完全依赖于异步关系。事实上,WinSNMP API中几乎所有函数都有异步成分,有些则是完全异步的。有三个非常重要的异步函数:

  • SnmpSendMsg (发送数据)

  • SnmpRecvMsg (接收数据)

  • SnmpRegister (注册接受trap消息)

WinSNMP的整个编程模式就是基于异步的,我们将在后面做详细介绍。


内存管理(Memory Management

Windows编程中,内存管理一向是一个令人头疼的问题。在这里,我们将对WinSNMP的内存管理做一个较为详尽的描述。

WinSNMP包括三种不同的内存“对象”:

  • 句柄式资源 (HANDLE’d Resources)

  • C风格(NULL结尾)的字符串

  • WinSNMP API结构类型


句柄式资源 (HANDLE’d Resources)

有五种句柄式资源的变量:

  • Sessions

  • Entities

  • Contexts

  • Protocol Data Units (PDUs)

  • VarBindLists (VBLs)

所有句柄对象都表示为“HSNMP_<object_tag>”的形式,它为WinSNMP实现(以DLL方式)所拥有。


 C风格字符串 (C-Stytle Strings)

C风格的字符串主要用来为通用的字符串表示与Entity和对象标识符(OID)对象之间的转换提供便利。WinSNMP中使用C风格字符串的函数有:SnmpStrToEntitySnmpEntityToStrSnmpStrToOidSnmpOidToStr

C风格字符串的内存分配、管理和释放完全由应用程序负责。因此我们还需要传递“size”参数给使用它的函数。


描述符 (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”值是一个smiOCTETSsmiOID对象时,我们就应该考虑内存管理,约定如下:

  1. 当其作为输入参数时,应用程序负责为变长对象分配内存;

  2. 当其作为输出参数时,由WinSNMP实现(表现为DLL)为变长对象分

内存。


内存的释放

WinSNMP应用程序必须负责释放所有通过调用WinSNMP API函数所分配的资源,主要有以下三类函数:

  • SnmpFree<xxx>: 释放EntityContextPduVblDescriptor

  • SnmpClose : 关闭会话

  • SnmpCleanup : 必须在程序结束之前调用,释放所有资源

应用程序推荐使用上述的顺序来释放所有的WinSNMP资源。




 WinSNMP基本编程模式

WinSNMP API按照SNMP协议封装了各种操作,包括PDUVarBindList以及协议操作的各项函数。我们可以按照SNMP协议的描述,调用WinSNMP相关函数,完成一次完整的SNMP。我们下面将以笔者完整的系统(采用SNMPv1协议)为例,具体描述WinSNMP的一般编程模式。我们分发送请求消息与接受响应消息两部分来实现。


 WinSNMP发送请求消息

WinSNMP发送请求消息的过程可以分为四个部分,主要有:WinSNMP的初始化、PDUs的创建、发送信息以及资源的释放。


WinSNMP的初始化

  1. 调用SnmpStartup函数启动WinSNMP

  2. 调用SnmpCreateSession函数创建一个会话session

  3. 调用SnmpSetRetransmitMode函数设置重传模式。

  4. 调用SnmpSetRetry函数设置重传次数。

  5. 调用SnmpSetTimeout函数设置超时时间。

其中第345步都是对本地数据库的操作,完成了对WinSNMP相关参数的设置。


创建协议数据单元(PDUs

在创建PDU之前,我们必须先创建变量绑定表(varbindlists)

(1) 调用SnmpStrToOid函数创建读取对象的OID,例如,我们创建MIB变量ipInReceives(一个实例的OID1.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_indexerror_statusrequest_id参数,它们都与协议中相应的量对应。

HSNMP_PDU m_hpdu=SnmpCreatePdu(session,SNMP_PDU_GET,

NULL,NULL,NULL,m_hvbl);


发送信息

我们首先调用SnmpStrToContextSnmpStrToEntity函数创建共同体(community)字符串和代理entity,具体实现见3.2.2

然后,我们调用SnmpSendMsg函数发送信息。

SnmpSendMsg(session,NULL,hAgent,hView,m_hpdu)


资源的释放

最后,我们应该释放所有分配的资源。


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网络管理程序的设计。但是,在具体的应用中,我们应该考虑更多的问题,如内存管理、错误处理等问题,还有很多问题需要我们在系统开发的过程中去发现、解决。下面,我将描述几个我在系统开发中遇到的问题,有的已经解决,有的还在探索中,希望能为同仁提供参考。


 几个问题


IP地址

前面讲到,IpAddressSMIv1的一个应用数据类型,表示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;


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我理解了你的要求。实现这个功能需要用到 WinSNMP API 来进行 SNMP 操作,以下是一个简单的 CManager 类实现代码: ```c #include <windows.h> #include <snmp.h> #pragma comment(lib, "snmpapi.lib") class CManager { public: CManager(); ~CManager(); bool Initialize(); bool Uninitialize(); bool GetAValue(const char* szOID, char* szValue, DWORD dwValueSize); bool GetTable(const char* szOID, char* szTable, DWORD dwTableSize, DWORD& dwTableRows); private: HSNMP_SESSION m_hSession; }; CManager::CManager() : m_hSession(NULL) { } CManager::~CManager() { Uninitialize(); } bool CManager::Initialize() { m_hSession = SnmpOpen(NULL, NULL, NULL, 0, SNMP_VERSION_2, SNMP_DEFAULT_RETRIES, SNMP_DEFAULT_TIMEOUT); if (m_hSession == NULL) { return false; } return true; } bool CManager::Uninitialize() { if (m_hSession != NULL) { SnmpClose(m_hSession); m_hSession = NULL; } return true; } bool CManager::GetAValue(const char* szOID, char* szValue, DWORD dwValueSize) { if (m_hSession == NULL) { return false; } SNMPAPI_STATUS nStatus; AsnObjectIdentifier snmpOID; snmpOID.idLength = MAX_OID_LEN; nStatus = SnmpMgrStrToOid(szOID, &snmpOID); if (nStatus != SNMPAPI_SUCCESS) { return false; } SNMP_VAR_BIND varBind; varBind.name = snmpOID; varBind.value.asnType = ASN_NULL; nStatus = SnmpMgrGet(m_hSession, &varBind); if (nStatus != SNMPAPI_SUCCESS) { return false; } if (varBind.value.asnType == ASN_NULL) { szValue[0] = '\0'; } else { DWORD dwLength = dwValueSize; SnmpUtilOidToA(&varBind.value.asnValue.object, szValue, &dwLength); } return true; } bool CManager::GetTable(const char* szOID, char* szTable, DWORD dwTableSize, DWORD& dwTableRows) { if (m_hSession == NULL) { return false; } SNMPAPI_STATUS nStatus; AsnObjectIdentifier snmpOID; snmpOID.idLength = MAX_OID_LEN; nStatus = SnmpMgrStrToOid(szOID, &snmpOID); if (nStatus != SNMPAPI_SUCCESS) { return false; } DWORD dwIndex = 0; while (1) { SNMP_VAR_BIND varBind; varBind.name = snmpOID; varBind.value.asnType = ASN_NULL; nStatus = SnmpMgrGetNext(m_hSession, &varBind); if (nStatus != SNMPAPI_SUCCESS) { break; } if (SnmpUtilOidNCmp(&varBind.name, &snmpOID, snmpOID.idLength) != 0) { break; } char szIndex[MAX_OID_LEN]; DWORD dwLength = MAX_OID_LEN; SnmpUtilOidToA(&varBind.name, szIndex, &dwLength); if (szTable != NULL) { if (dwIndex == 0) { strcpy(szTable, szIndex); } else { strcat(szTable, ";"); strcat(szTable, szIndex); } strcat(szTable, "="); switch (varBind.value.asnType) { case ASN_INTEGER: char szValue[32]; sprintf(szValue, "%d", varBind.value.asnValue.number); strcat(szTable, szValue); break; case ASN_OCTETSTRING: strncat(szTable, (char*)varBind.value.asnValue.string.stream, varBind.value.asnValue.string.length); break; default: strcat(szTable, "unknown type"); break; } } dwIndex++; } dwTableRows = dwIndex; return true; } ``` 这个类中包含了 Initialize() 和 Uninitialize() 方法来初始化和释放 WinSNMP 会话,GetAValue() 方法用于获取一个 OID 的值,GetTable() 方法用于获取一组 OID 的值。在 GetTable() 方法中,我们使用 SnmpMgrGetNext() 方法来获取下一个 OID 的值,这样可以遍历整个 OID 表并获取所有 OID 的值。 以下是一个简单的测试程序: ```c int main() { CManager manager; if (!manager.Initialize()) { printf("Failed to initialize WinSNMP.\n"); return 0; } // Test GetAValue char szValue[MAX_PATH]; if (manager.GetAValue("1.3.6.1.2.1.1.5.0", szValue, MAX_PATH)) { printf("System name: %s\n", szValue); } else { printf("Failed to get system name.\n"); } // Test GetTable char szTable[MAX_PATH]; DWORD dwTableRows; if (manager.GetTable("1.3.6.1.2.1.25.4.2.1.2", szTable, MAX_PATH, dwTableRows)) { printf("Processes:\n"); printf("%s\n", szTable); printf("Total rows: %d\n", dwTableRows); } else { printf("Failed to get processes.\n"); } manager.Uninitialize(); return 0; } ``` 这个测试程序分别测试了 GetAValue 和 GetTable 方法,使用的 OID 分别是系统名称和进程列表。注意,OID 的格式是以“.”分隔的数字,例如 1.3.6.1.2.1.1.5.0 表示系统名称。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值