使用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一樣的通過監聽端接收麼?

有了解的大佬評論給指個路 謝謝(

 

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