zoukankan      html  css  js  c++  java
  • 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;

    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函数设置超时时间。
    其中第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;
       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添加路由表时,必须指定所有的参数。并且,表格变量只允许添加与删除两种操作。

  • 相关阅读:
    java发送qq邮件
    HTTP3次握手和4次挥手
    Bootstrap面试题
    Bootstrap
    响应式布局
    JQuery思维导图
    JQuery相关知识点和面试题
    CSS思维导图
    前端面试题
    CSS3实现跑马效果
  • 原文地址:https://www.cnblogs.com/xumaojun/p/8528919.html
Copyright © 2011-2022 走看看