使用snmp4j实现snmp通讯Trap发送与接收中文处理

记录snmp使用过程中的小问题。

1.maven工程依赖

<dependency>
   <groupId>org.snmp4j</groupId>
   <artifactId>snmp4j</artifactId>
   <version>2.8.7</version>
</dependency>

 

2.网上找到的收发方法(V2c)

import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.io.IOException;

public class TestTrap {

    private Snmp snmp = null;
    private Address targetAddress = null;
    private TransportMapping<UdpAddress> transport = null;

    public void init() throws IOException {
        //目标主机的ip地址 和 端口号 (127.0.0.1是本机调试)
        targetAddress = GenericAddress.parse("udp:127.0.0.1/162");
        transport = new DefaultUdpTransportMapping();
        snmp = new Snmp(transport);
        transport.listen();
    }

    /**
     * Snmp V2c 测试发送Trap
     * @return
     * @throws IOException
     */
    public ResponseEvent sendV2cTrap() throws IOException {
        PDU pdu = new PDU();
        VariableBinding v = new VariableBinding();
        v.setOid(new OID("1.21.1.2.2.3.45.5.57567.3"));//随便写的oid
        v.setVariable(new OctetString("Snmp Trap V2 Test 123"));
        v.setOid(new OID("1.21.1.2.2.3.45.5.57567.4"));//随便写的oid 
        v.setVariable(new OctetString("你好 Snmp Trap V2 Test 123"));
        pdu.add(v);
        pdu.setType(PDU.TRAP);

        // set target
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString("public"));
        target.setAddress(targetAddress);

        // retry times when commuication error
        target.setRetries(2);
        target.setTimeout(1500);
        target.setVersion(SnmpConstants.version2c);
        // send pdu, return response
        return snmp.send(pdu, target);
    }

    public static void main(String[] args) {
        TestTrap poc = new TestTrap();
        try {
            poc.init();
            poc.sendV2cTrap();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
import cn.sh.ideal.service.NmsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.*;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.BindException;
import java.time.Duration;
import java.time.Instant;

/** 
 * 本类用于监听代理进程的Trap信息 
 * 实现CommandResponder接口的监听器
 */
@Component
public class MultiThreadedTrapReceiver implements CommandResponder {

    private MultiThreadedMessageDispatcher dispatcher;
    private Snmp snmp = null;
    private Address listenAddress;  
    private ThreadPool threadPool;

    @PostConstruct
    public void run() {
        try {
            init();
            snmp.addCommandResponder(this);//注册命令响应监听器
            System.out.println("开始监听Trap信息!");
        } catch (BindException be) {
            System.out.println("--初始化snmp本地监听失败,地址/端口无法绑定,请确认本地snmp监听ip和端口配置 "+be.getMessage());
        } catch (Exception ex) {
            System.out.println("--初始化snmp本地监听端口报错"+ex.getMessage());
            ex.printStackTrace();
        }
    }

    private void init() throws IOException {
        System.out.println("snmp udp localAddress:"+localAddress);
        //创建接收SnmpTrap的线程池,参数: 线程名称及线程数
        threadPool = ThreadPool.create("TrapListen", "2");
        dispatcher = new MultiThreadedMessageDispatcher(threadPool,
                new MessageDispatcherImpl());
        //监听端的 ip地址 和 监听端口号
        listenAddress = GenericAddress.parse(System.getProperty(
                "snmp4j.listenAddress", "udp:127.0.0.1/162"));

        TransportMapping<?> transport;
        if (listenAddress instanceof UdpAddress) {
            transport = new DefaultUdpTransportMapping((UdpAddress) listenAddress);
        } else {
            transport = new DefaultTcpTransportMapping((TcpAddress) listenAddress);
        }
        snmp = new Snmp(dispatcher, transport);
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());

        //开启Snmp监听,可以接收来自Trap端的信息。
        snmp.listen();

    }

    /** 
     * 实现CommandResponderprocessPdu方法, 用于处理传入的请求、PDU等信息 
     * @param respEvnt
     */  
    public void processPdu(CommandResponderEvent respEvnt) {
        // 解析Response
        if (respEvnt != null && respEvnt.getPDU() != null) {
            Vector<VariableBinding> recVBs = (Vector<VariableBinding>) respEvnt.getPDU().getVariableBindings();  
            for (int i = 0; i < recVBs.size(); i++) {  
                 VariableBinding recVB = recVBs.elementAt(i);  
                 System.out.println("OID: "+recVB.getOid() + " `,Variable: " + recVB.getVariable());
            }
        }
    }

}

 

3.问题

接收端输出日志中 Variable 不带中文时正常,带中文时会被转成16进制。

System.out.println(new OctetString("Snmp Test"));
System.out.println(new OctetString("Snmp Test 你好"));

OctetString处理中文的锅

 

4.解决方案

public static String getChinese(String octetString) {    //snmp4j遇到中文直接转成16进制字符串
    String str = "";
    try {
        String[] temps = octetString.split(":");
        byte[] bs = new byte[temps.length];
        for (int i = 0; i < temps.length; i++) {
            if (octetString.length() == 17) {
                str = str + temps[i] + "-";
            } else {
                bs[i] = (byte) Integer.parseInt(temps[i], 16);
            }
        }
        if (octetString.length() == 17) {
            str = str.substring(0, str.length() - 1);
        } else {
            str = new String(bs, "UTF-8");
        }
    } catch (Exception e) {
        return null;
    }
    return str;
}

网上找的OctetString中文16进制字符串转回来方法,能转回中文。但是如果处理不带中文的OctetString会报错。

 

于是看了下OctetString类提供的方法,改了改目前用着没问题。

System.out.println("OID: "+recVB.getOid() + " `,Variable: " + getChinese((OctetString) recVB.getVariable()));
public static String getChinese(OctetString octetString) {    //snmp4j遇到中文直接转成16进制字符串
        if(octetString.isPrintable()){
            return octetString.toString();
        }
        return new String(OctetString.fromHexString(octetString.toString()).getValue());
    }

 

2021-12-23追加一个小优化

请求端发Trap时Variable里面放OctetString没问题 但是放OID时,转格式 (OctetString) recVB.getVariable() 这里报错了 而且没有报错日志

加了个数据类型为OID判断的容错

System.out.println("OID: "+recVB.getOid() + " ,Variable: " + SnmpSendUtil.getChinese(recVB.getVariable()));
public static String getChinese(Variable obj) {    //snmp4j遇到中文直接转成16进制字符串
    if(obj instanceof OID)
        return obj.toString();

    OctetString octetString = (OctetString)obj;
    if(octetString.isPrintable()){
        return octetString.toString();
    }
    return new String(OctetString.fromHexString(octetString.toString()).getValue());
}

 

5.划重点

目前还没搞明白snmp4j怎么实现服务端被GET时候返回PDU给请求端

snmp4j能实现服务端被GET么?是和被TRAP一样的通过监听端接收么?

有了解的大佬评论给指个路 谢谢(

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章