WINSNMP的简单使用

背景

如上一篇博客所说,因为项目上需要做一个snmp通信的上位机,之前一直在编译第三方的库,但是最后选择了Windows上的官方的库WINSNMP。网上查了老半天,百度上全是相同的一篇博客,然后google了一下,收获也不是很大,然后就只有自己啃一啃MS的官方文档。由于本人也是初次接触snmp,本文只是简单的说明一下WINSNMP的使用,并不对API函数进行讲解,如果文章有不足之处还望指出。

MS官方说明文档地址:https://docs.microsoft.com/zh-cn/windows/desktop/SNMP/winsnmp-api

正文

通信的初始化

使用WINSNMP第一步首先需要调用SnmpStartup()函数程序才能正常调用其他的WINSNMP函数,该函数的参数主要是用于返回一些参数。在调用该函数过后,就可以创建snmp通信的会话(session)了,一个程序可以创建多个session,这个暂时没有研究,在创建会话时,可以选择传入回调函数用以处理事件,也可以通过该函数的第二个参数wMsg来使用Windows的消息处理机制,我选择了前者传入回调函数:

void SNMP::startSNMP(void)
{
    smiUINT32 major, minor;
    smiUINT32 level;
    smiUINT32 tslMode;
    smiUINT32 tsmMode;

    if (SnmpStartup(&major, &minor, &level, &tslMode, &tsmMode) == SNMPAPI_FAILURE) {
        qDebug() << "SNMP service open failed.";
        return ;
    }
    qDebug("Version:v%ld.%ld, level:%ld, translate mode:%ld, transmit mode:%ld\n", major, minor, level, tslMode, tsmMode);

    m_hSession = SnmpCreateSession(Q_NULLPTR, 0, sessionCallback, this);
    if (m_hSession == Q_NULLPTR) {
        qDebug() << "SNMP create session failed.";
        return ;
    }
}

在启动snmp功能和建立好一个会话过后,我们要进行数据交互首先需要创建一个交互的实体(entity)和上下文(context),其实简单一点就是设置目标的IP地址和community,这种说话可能不太准确。

void SNMP::setEntity(const QString &entity)
{
    m_hEntity = SnmpStrToEntity(m_hSession, entity.toLocal8Bit().constData());
}


void SNMP::setCommunity(const QString &community)
{
    smiOCTETS contextName;

    contextName.ptr = reinterpret_cast<smiLPBYTE>(community.toLocal8Bit().data());
    contextName.len = static_cast<quint32>(community.size());

    m_hContext = SnmpStrToContext(m_hSession, &contextName);
}

然后还可以设置重传模式和参数还有传输协议的模式,这里不着重去讲。

数据读写

数据的读写主要是通过变量绑定列表(VBL)和协议数据单元PDU实现。

写入

数据主要是放在VBL中,可以通过SnmpCreateVbl()函数创建一个新的VBL用于保存数据,如果在调用该函数时就传入数据,那么数据将保存在VBL的第一位,然后可以通过SnmpSetVb()将多个输入放入一个VBL中,当要传输的数据都放入到VBL中后,就可以通过SnmpCreatePdu()函数又放入PDU中,最后通过SnmpSendMsg()发送出去:

void SNMP::sendData(long pduType, quint32 dataType, const QString &oid, const QVariant &data)
{
    HSNMP_VBL vbl;
    vbl = SnmpCreateVbl(m_hSession, Q_NULLPTR, Q_NULLPTR);

    /* package value */
    smiVALUE value;
    memset(&value, 0, sizeof(value));

    value.syntax = dataType;
    switch (dataType) {
    case SNMP_SYNTAX_INT:       // int number
        value.value.sNumber = data.toInt();
        break;
    case SNMP_SYNTAX_UINT32:    // unsigned 32bit number
        value.value.uNumber = data.toUInt();
        break;
    case SNMP_SYNTAX_CNTR64:    // 64bit number
        value.value.hNumber.hipart = (data.toULongLong() >> 32);
        value.value.hNumber.lopart = (data.toULongLong() & 0xFFFFFFFF);
        break;
    case SNMP_SYNTAX_OCTETS:    // string
        value.value.string.ptr = reinterpret_cast<smiLPBYTE>(data.toByteArray().data());
        value.value.string.len = static_cast<quint32>(data.toByteArray().size());
        break;
    default:
        break;
    }

    smiOID Oid;
    SnmpStrToOid(oid.toLocal8Bit().constData(), &Oid);

    SnmpSetVb(vbl, 0, &Oid, &value);
    HSNMP_PDU pdu = SnmpCreatePdu(m_hSession, pduType, 0, 0, 0, vbl);

    SnmpSendMsg(m_hSession, Q_NULLPTR, m_hEntity, m_hContext, pdu);

    SnmpFreePdu(pdu);
    SnmpFreeVbl(vbl);
}

接收

由于采用的异步回调机制,当接收到数据时,首先会调用回调函数,然后在回调函数中我这只是做了一个信号触发,不过这里并没有判断回调事件是否是接收数据,所以可能存在问题:

static SNMPAPI_STATUS CALLBACK sessionCallback(HSNMP_SESSION hSession, HWND hWnd, UINT wMsg,
                               WPARAM wParam, LPARAM lParam, LPVOID lpClientData)
{
    Q_UNUSED(hSession);
    Q_UNUSED(hWnd);
    Q_UNUSED(wMsg);
    Q_UNUSED(wParam);
    Q_UNUSED(lParam);

    SNMP *snmp = static_cast<SNMP *>(lpClientData);

    emit snmp->readyRead();

    return SNMPAPI_SUCCESS;
}

在槽函数中,只需要调用Recv()函数就可以返回当前读取到的数据了:

void MainWindow::test(void)
{
    qDebug() << "receive data: " << snmp->recvData().toByteArray() << "\n";

    snmp->getNext();
}

这里需要注意的是,有些时候我们需要获取一堆的数据,这时候我们可以通过设置PDU为SNMP_PDU_GETNEXT实现:

void SNMP::traverse(const QString &sOid, const QString &eOid)
{
    if (!eOid.isEmpty() && eOid.compare(sOid) < 0) {
        qDebug() << "SNMP end oid is invaild.";
        return ;
    }

    m_sTraSOid = sOid;
    m_sTraEOid = eOid;

    sendData(SNMP_PDU_GETNEXT, SNMP_SYNTAX_NULL, sOid, QVariant(QVariant::Invalid));

    m_bNext = true;
}

当接收到数据时,就调用getNext()用以获取下一个oid的数据:

void SNMP::getNext(void)
{
    if (m_bNext && (m_sTraSOid.compare(m_sTraEOid) > 0)) {
        m_bNext = false;
        return ;
    }

    if (m_bNext) {
        sendData(SNMP_PDU_GETNEXT, SNMP_SYNTAX_NULL, m_sTraSOid, QVariant(QVariant::Invalid));
    }
}

在接收函数中,需要做的就是接收PDU,从PDU中获取VBL,然后从VBL中再获取到数据,其中VBL中的smiVALUE

的syntax值为数据类型,可以根据它来判断VBL中什么数据是我们想要的:

QVariant SNMP::recvData(void)
{
    HSNMP_PDU pdu;

    if (SnmpRecvMsg(m_hSession, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, &pdu)) {
        qDebug() << "SNMP receive data failed.";
        return QVariant(QVariant::Invalid);
    }

    smiINT pduType = 0;
    HSNMP_VBL vbl;
    SnmpGetPduData(pdu, &pduType, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, &vbl);

//    qDebug() << "Num: " << SnmpCountVbl(vbl);

    smiOID oid;
    smiVALUE value;
    SnmpGetVb(vbl, 1, &oid, &value);

    char strOid[SNMP_MAX_OID_SIZE] = { 0 };
    SnmpOidToStr(&oid, 256, strOid);

    if (m_bNext) {
        m_sTraSOid = QByteArray(strOid);
//        qDebug() << "OID: " << m_sTraSOid;
    }

    QVariant data;
    switch (value.syntax) {
    case SNMP_SYNTAX_NULL:
    case SNMP_SYNTAX_NOSUCHOBJECT:
    case SNMP_SYNTAX_NOSUCHINSTANCE:
    case SNMP_SYNTAX_ENDOFMIBVIEW:
        data = QVariant(QVariant::Invalid);
        break;
    case SNMP_SYNTAX_OCTETS:
    case SNMP_SYNTAX_BITS:
    case SNMP_SYNTAX_OPAQUE:
    case SNMP_SYNTAX_IPADDR:
    case SNMP_SYNTAX_NSAPADDR:
        data = QVariant(QByteArray(reinterpret_cast<const char *>(value.value.string.ptr),
                                   static_cast<int>(value.value.string.len)));
        break;
    case SNMP_SYNTAX_INT:
    case SNMP_SYNTAX_UINT32:
    case SNMP_SYNTAX_CNTR32:
    case SNMP_SYNTAX_GAUGE32:
    case SNMP_SYNTAX_TIMETICKS:
    case SNMP_SYNTAX_CNTR64:
        data = QVariant(static_cast<qulonglong>(value.value.sNumber));
        break;
    default:
        break;
    }

    SnmpFreeDescriptor(value.syntax, &value.value.string);
    SnmpFreePdu(pdu);
    SnmpFreeVbl(vbl);

    return data;
}

这里我只是简单的将一些数据归类到了一起,可能不太准确,有需要的同学可以自己再细分。

Bulk

要使用SNMP_PDU_GETBULK必须设置SnmpSetTranslateMode(SNMPAPI_UNTRANSLATED_V2);因为Bulk是在SNMP v2中加入,如果使用传送模式为v2将只会获取到第一个Oid的内容。

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值