用VC++6.0实现SNMP协议的方法
SNMP(Simple Network Management Protocol)是简单网络管理协议,主要用来管理网络设备,正因为“简单”,所以其发展很快,到目前为止几乎所有的网络产品都要为其提供支持,以方便管理员的管理和软件开发人员的开发。
在这里我们不对有关SNMP的概念和基础知之作过多地阐述,一且假定你对其工作原理有了一定的了解,而仅缺的是如何去实现他。
我们所阐述的是基于VC6.0下的SNMP编程。详细介绍一下有关SNMP编程的过程及API函数的用法,在遇到难于理解的部分,我会以较大的篇幅作以解释。言归正传,进入我们的主题。
首先来阐述几个重要的概念:
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)管理信息库。对于他们所标识的意义,读者可以到网上查询一下,提供一个简单的寻找办法,你可以进入google,直接输入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
);
第二个函数并没有被完全确定下来,他只是为程序员在编程过程中不是基于windows的编程提供一种选择,第一个参数指向接收消息的窗口句炳,第二个参数则指向该窗口需要接收的消息码。该函数返回一个会话句炳,这一句炳是在我们以下程序中都要用到的一个重要变量。
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 identifies
// the entity
);
该函数的第一个参数是第二步返回的会话句炳,第二个参数与你在第三步中设置的传输模式有关,如果你选则后两个参数,那么这里的string就是你要发送消息的网络设备ip地址或接收消息的管理设备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 object 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_<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, *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 source 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()解决他们吧。
一且到此完结,大概步骤就这些了,你也许对SNMP_PDU_SET和SNMP_PDU_TRAP有些不解,前者你可以在第10步追加绑定列表中改变变量值,当然要遵循smiVALUE的结构,将类型和值都添上,填好需要改变的对象标志符,同时在第11步创建PDU中将类型设为SNMP_PDU_SET就行了。
对于想进行SNMP编程的人员,在下以菜鸟的身份给你们提个醒,SNMP编成的过程很死,但精心的设计会使你的程序更加的健壮、高效和容易扩展,我强烈的建议你们看看hp的snmp++,他的源代码很有层次,极易扩展