之前有在弄监控服务器这块的工作,今天来整体总结下。因为有些服务器(路由器、交换机等都是基于 snmp 协议的)必须使用 snmp 协议去监控采集和接收信息,所以必须去了解 snmp 相关内容,以及如何在基于 java 上开发。关于了解 snmp 相关内容,必看《SNMP 简单网络管理协议》这本书里面介绍的很详细,另外推荐这位前辈的博文写的很到位 《snmp 学习总结》 。关于 snmp4j 的介绍也可以看看前面这位前辈关于 《snmp 学习总结》 的最后一篇博文 《snmp4j 介绍》 。当然本篇主要记录如何基于 Java 如何使用 snmp4j 去开发实现监控与采集,下面我们直接结合源码以及实例讲解:
一、针对源码进行分析:
1、核心对象 SNMP 的初始化。
源码中有四种初始化方法及四个构造函数,其实都大同小异:参数少的就必须后续添加,参数多的必须提前初始化。
我们先看看第一个无参构造函数,源码很简单,但是注释很多,所以看源码必须要先看注释。
- /**
- * Creates a {@code Snmp} instance that uses a
- * {@code MessageDispatcherImpl} with no message processing
- * models and no security protols (by default). You will have to add
- * those by calling the appropriate methods on
- * {@link #getMessageDispatcher()}.
- * <p>
- * At least one transport mapping has to be added before {@link #listen()}
- * is called in order to be able to send and receive SNMP messages.
- * <p>
- * To initialize a {@code Snmp} instance created with this constructor
- * follow this sample code:
- * <pre>
- * Transport transport = ...;
- * Snmp snmp = new Snmp();
- * SecurityProtocols.getInstance().addDefaultProtocols();
- * MessageDispatcher disp = snmp.getMessageDispatcher();
- * disp.addMessageProcessingModel(new MPv1());
- * disp.addMessageProcessingModel(new MPv2c());
- * snmp.addTransportMapping(transport);
- * OctetString localEngineID = new OctetString(
- * MPv3.createLocalEngineID());
- * // For command generators, you may use the following code to avoid
- * // engine ID clashes:
- * // MPv3.createLocalEngineID(
- * // new OctetString("MyUniqueID"+System.currentTimeMillis())));
- * USM usm = new USM(SecurityProtocols.getInstance(), localEngineID, 0);
- * disp.addMessageProcessingModel(new MPv3(usm));
- * snmp.listen();
- * </pre>
- */
- public Snmp() {
- this.messageDispatcher = new MessageDispatcherImpl();
- if (SNMP4JSettings.getSnmp4jStatistics() != SNMP4JSettings.Snmp4jStatistics.none) {
- counterSupport = CounterSupport.getInstance();
- }
- }
从上面注释中可以看出要初始化 snmp 需要设置 messageDispatcher 里面的参数和 TransportMapping 参数,如果没有设置好这个两个参数,发送报文时会报错(见下面案例). 所以我们可以直接使用第三个构造函数。
接下来我们来看第二个构造函数 Snmp(TransportMapping<? extends Address> transportMapping):
- /**
- * Creates a <code>Snmp</code> instance that uses a
- * <code>MessageDispatcherImpl</code> with all supported message processing
- * models and the default security protols for dispatching.
- * <p>
- * To initialize a <code>Snmp</code> instance created with this constructor
- * follow this sample code:
- * <pre>
- * Transport transport = ...;
- * Snmp snmp = new Snmp(transport);
- * OctetString localEngineID =
- * new OctetString(snmp.getMPv3().getLocalEngineID());
- * USM usm = new USM(SecurityProtocols.getInstance(), localEngineID, 0);
- * SecurityModels.getInstance().addSecurityModel(usm);
- * snmp.listen();
- * </pre>
- *
- * @param transportMapping TransportMapping
- * the initial <code>TransportMapping</code>. You can add more or remove
- * the same later.
- */
- public Snmp(TransportMapping < ?extends Address > transportMapping) {
- this();
- initMessageDispatcher();
- if (transportMapping != null) {
- addTransportMapping(transportMapping);
- }
- }
- protected final void initMessageDispatcher() {
- this.messageDispatcher.addCommandResponder(this);
- this.messageDispatcher.addMessageProcessingModel(new MPv2c());
- this.messageDispatcher.addMessageProcessingModel(new MPv1());
- this.messageDispatcher.addMessageProcessingModel(new MPv3());
- SecurityProtocols.getInstance().addDefaultProtocols();
- }
从源码中可以看到它帮我们设置了 messageDispatcher 里面的参数,只要我们提供 TransportMapping 参数即可。第四个构造函数 Snmp(MessageDispatcher messageDispatcher) 其实跟第一个类似同样需要提供两个参数,所以第三个和第四个就列出来了。其中涉及到接口有 MessageDispatcher 接口、MessageProcessingModel 接口,涉及到的类有 MPv1、MPv2 和 MPv3 分别对应 snmp 版本 v1、v2c 和 v3。
- /**
- * @description MessageDispatcher接口定义了处理传入的SNMP消息并将其分派到感兴趣的CommandResponder实例的实例的公共服务。它还提供了一个发送出去的SNMP消息的服务。
- */
- public interface MessageDispatcher extends TransportListener {}
- /**
- * @description MessageProcessingModel 接口为所有SNMP消息处理模型定义了通用方法。
- */
- public interface MessageProcessingModel {}
- /**
- * @description TransportMapping定义了SNMP传输映射的公共接口。传输映射只能支持单个传输协议。
- */
- public interface TransportMapping < A extends Address > {}
2、核心对象 Target
我们先看下 Target 对象下的继承关系。其中主要用到的子对象是 CommunityTarget 和 UserTarget,CommunityTarget 用于 SNMPv1 和 SNMPv2c 这两个版本,而 UserTarget 用于 SNMPV3 版本。
在初始化 CommunityTarget 时默认使用的是 snmpv1(适用于 snmpv2).
/** * Default constructor. */ public CommunityTarget() { setVersion(SnmpConstants.version1); setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1); }
3、核心对象 PDU
跟 Target 一样,针对 snmp 的不同版本是使用不同的子类去实现。PDUv1 用于 SNMPv1 和 SNMPv2c 这两个版本,而 ScopedPDU 用于 SNMPV3 版本。
二、案例分析:
1、snmpGet 功能测试:
第一步:要初始 snmp 并开启监听。其中有点不同的是,为了支持 snmpv3 版本的处理需要增加用户并设置安全名称和加密算法。(关于那些静态变量的值,最好放到配置文件中显得灵活点)。
另外再说明下:snmp 是基于 udp 协议发送报文的,且 snmp 端口默认为 161。
public class SnmpUtil { private static Logger log = LoggerFactory.getLogger(SnmpUtil.class); public static Snmp snmp = null; private static String community = "public"; private static String ipAddress = "udp:10.10.112.105/"; /** * @description 初始化snmp * @author YuanFY * @date 2017年12月16日 上午10:28:01 * @version 1.0 * @throws IOException */ public static void initSnmp() throws IOException { //1、初始化多线程消息转发类 MessageDispatcher messageDispatcher = new MessageDispatcherImpl(); //其中要增加三种处理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加 messageDispatcher.addMessageProcessingModel(new MPv1()); messageDispatcher.addMessageProcessingModel(new MPv2c()); //当要支持snmpV3版本时,需要配置user OctetString localEngineID = new OctetString(MPv3.createLocalEngineID()); USM usm = new USM(SecurityProtocols.getInstance().addDefaultProtocols(), localEngineID, 0); UsmUser user = new UsmUser(new OctetString("SNMPV3"), AuthSHA.ID, new OctetString("authPassword"), PrivAES128.ID, new OctetString("privPassword")); usm.addUser(user.getSecurityName(), user); messageDispatcher.addMessageProcessingModel(new MPv3(usm)); //2、创建transportMapping UdpAddress updAddr = (UdpAddress) GenericAddress.parse("udp:10.10.112.177/161"); TransportMapping < ?>transportMapping = new DefaultUdpTransportMapping(updAddr); //3、正式创建snmp snmp = new Snmp(messageDispatcher, transportMapping); //开启监听 snmp.listen(); } }
其中要注意的是 UdpAddress updAddr = (UdpAddress) GenericAddress.parse("udp:10.10.112.177/161"); 只能指定本机 ip,要么不要设置地址。请看 DefaultUdpTransportMapping 的源码
/** * Creates a UDP transport on the specified address. The address will not be * reused if it is currently in timeout state (TIME_WAIT). * * @param udpAddress * the local address for sending and receiving of UDP messages. * @throws IOException * if socket binding fails. */ public DefaultUdpTransportMapping(UdpAddress udpAddress) throws IOException { super(udpAddress); socket = new DatagramSocket(udpAddress.getPort(), udpAddress.getInetAddress()); }
第二步: 根据 snmp 版本创建 Target 对象,其中针对 snmpV3 版本需要设置安全级别和安全名称,其中安全名称是创建 snmp 指定 user 设置的 new OctetString("SNMPV3"),针对 snmpv1 和 snmpv2c 需要设置团体名。另外必须设置 ipAddress,且对应的主机要配置 snmp 否则获取不到值。如下:
private static Target createTarget(int version, int port) { Target target = null; if (! (version == SnmpConstants.version3 || version == SnmpConstants.version2c || version == SnmpConstants.version1)) { log.error("参数version异常"); return target; } if (version == SnmpConstants.version3) { target = new UserTarget(); //snmpV3需要设置安全级别和安全名称,其中安全名称是创建snmp指定user设置的new OctetString("SNMPV3") target.setSecurityLevel(SecurityLevel.AUTH_PRIV); target.setSecurityName(new OctetString("SNMPV3")); } else { //snmpV1和snmpV2需要指定团体名名称 target = new CommunityTarget(); ((CommunityTarget) target).setCommunity(new OctetString(community)); if (version == SnmpConstants.version2c) { target.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c); } } target.setVersion(version); //必须指定,没有设置就会报错。 target.setAddress(GenericAddress.parse(ipAddress + port)); target.setRetries(5); target.setTimeout(3000); return target; }
第三步:创建报文。其中要注意的是 pdu 可以设置类型,如果想要用 snmpget 方法,就设置 PDU.GET.
private static PDU createPDU(int version, int type, String oid) { PDU pdu = null; if (version == SnmpConstants.version3) { pdu = new ScopedPDU(); } else { pdu = new PDUv1(); } pdu.setType(type); //可以添加多个变量oid pdu.add(new VariableBinding(new OID(oid))); return pdu; }
最后一步发送报文也是最重要的一步,需要前面三步的支撑才能进行。如下:
public static void snmpGet(String oid) { try { //1、初始化snmp,并开启监听 initSnmp(); //2、创建目标对象 Target target = createTarget(SnmpConstants.version2c, SnmpConstants.DEFAULT_COMMAND_RESPONDER_PORT); //3、创建报文 PDU pdu = createPDU(SnmpConstants.version2c, PDU.GET, oid); System.out.println("-------> 发送PDU <-------"); //4、发送报文,并获取返回结果 ResponseEvent responseEvent = snmp.send(pdu, target); PDU response = responseEvent.getResponse(); System.out.println("返回结果:" + response); } catch(IOException e) { e.printStackTrace(); } }
测试如下:
public static void main(String[] args) { snmpGet("1.3.6.1.2.1.1.1.0"); }
output:
-------> 发送PDU <------- 返回结果:RESPONSE[requestID=1344419162, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.1.1.0 = Linux localhost.localdomain 3.10.0-327.36.2.el7.x86_64 #1 SMP Mon Oct 10 23:08:37 UTC 2016 x86_64]]
从中可以得知,snmpget 是可以根据指定的 oid 获取其对应的内容的。
2、SNMPWalk 功能测试
查看了下 PDU 的源码,发现没有对应 snmpwalk 的类型,所以使用 getNext 类型来实现 snmpwalk 功能。
public static void snmpWalk(String oid) { try { //1、初始化snmp,并开启监听 initSnmp(); //2、创建目标对象 Target target = createTarget(SnmpConstants.version2c, SnmpConstants.DEFAULT_COMMAND_RESPONDER_PORT); //3、创建报文 PDU pdu = createPDU(SnmpConstants.version2c, PDU.GETNEXT, oid); System.out.println("-------> 发送PDU <-------"); //4、发送报文,并获取返回结果 boolean matched = true; while (matched) { ResponseEvent responseEvent = snmp.send(pdu, target); if (responseEvent == null || responseEvent.getResponse() == null) { break; } PDU response = responseEvent.getResponse(); String nextOid = null; Vector < ?extends VariableBinding > variableBindings = response.getVariableBindings(); for (int i = 0; i < variableBindings.size(); i++) { VariableBinding variableBinding = variableBindings.elementAt(i); Variable variable = variableBinding.getVariable(); nextOid = variableBinding.getOid().toDottedString(); //如果不是这个节点下的oid则终止遍历,否则会输出很多,直到整个遍历完。 if (!nextOid.startsWith(oid)) { matched = false; break; } //System.out.println(variable); } if (!matched) { break; } pdu.clear(); pdu.add(new VariableBinding(new OID(nextOid))); System.out.println("返回结果:" + response); } } catch(IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
测试如下:
public static void main(String[] args) { //snmpGet("1.3.6.1.2.1.1.1.0"); snmpWalk("1.3.6.1.2.1.25.3.3.1.2");//CPU的当前负载,N个核就有N个负载4 }
------->发送PDU < -------
返回结果:RESPONSE[requestID=1014693266, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.25.3.3.1.2.196608 = 1]]
返回结果:RESPONSE[requestID=1014693268, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.25.3.3.1.2.196609 = 0]]
用命令获取的结果是跟代码输出的结果是一样的,如下:
3、前面两个案例都是跟采集有关,接下来介绍如何监控接收服务器发过来的故障然后提示个用户,这就需要用到 snmptrap 了。接下来我们直接看案例:
处理流程:
1、必须实现 CommandResponder 接口
2、初始化 snmp 并开启监听。这步跟上面初始化一样,只是面对并发的情况使用 MultiThreadedMessageDispatcher 进行信息处理。
3、将当前实现 CommandResponder 的对象添加至 snmp 的 addCommandResponder 才能接收到信息。
4、处理接收到信息,通知用户。
package com.yuanfy.study.snmp; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.CommandResponder; import org.snmp4j.CommandResponderEvent; import org.snmp4j.MessageDispatcher; import org.snmp4j.MessageDispatcherImpl; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.mp.MPv1; import org.snmp4j.mp.MPv2c; import org.snmp4j.mp.MPv3; import org.snmp4j.security.AuthSHA; import org.snmp4j.security.PrivAES128; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.UdpAddress; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.snmp4j.util.MultiThreadedMessageDispatcher; import org.snmp4j.util.ThreadPool; public class SnmpTrapHandler implements CommandResponder { private static Logger log = LoggerFactory.getLogger(SnmpTrapHandler.class); private static int threadNum = 200; private static String ipAddress = "udp:10.10.112.177/162"; private Snmp snmp = null; public void init() { //1、初始化多线程消息转发类 ThreadPool threadPool = ThreadPool.create("SnmpTrap", threadNum); MessageDispatcher messageDispatcher = new MultiThreadedMessageDispatcher(threadPool, new MessageDispatcherImpl()); //其中要增加三种处理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加 messageDispatcher.addMessageProcessingModel(new MPv1()); messageDispatcher.addMessageProcessingModel(new MPv2c()); OctetString localEngineID = new OctetString(MPv3.createLocalEngineID()); USM usm = new USM(SecurityProtocols.getInstance().addDefaultProtocols(), localEngineID, 0); UsmUser user = new UsmUser(new OctetString("SNMPV3"), AuthSHA.ID, new OctetString("authPassword"), PrivAES128.ID, new OctetString("privPassword")); usm.addUser(user.getSecurityName(), user); messageDispatcher.addMessageProcessingModel(new MPv3(usm)); //2、创建transportMapping TransportMapping < ?>transportMapping = null; try { UdpAddress updAddr = (UdpAddress) GenericAddress.parse(System.getProperty("snmp4j.listenAddress", ipAddress)); transportMapping = new DefaultUdpTransportMapping(updAddr); //3、正式创建snmp snmp = new Snmp(messageDispatcher, transportMapping); //开启监听 snmp.listen(); } catch(IOException e) { log.error("初始化transportMapping失败:", e.getMessage()); e.printStackTrace(); } } public void start() { init(); //一定要将当前对象添加至commandResponderListeners中 snmp.addCommandResponder(this); System.out.println("开始监听trap信息:"); } /** * 处理信息方法 */ @Override public void processPdu(CommandResponderEvent event) { String version = null; String community = null; if (event.getPDU().getType() == PDU.V1TRAP) { version = "v1"; community = new String(event.getSecurityName()); } else if (event.getPDU().getType() == PDU.TRAP) { if (event.getSecurityModel() == 2) { version = "v2"; community = new String(event.getSecurityName()); } else { version = "v3"; } } System.out.println("接收到的trap信息:[发送来源=" + event.getPeerAddress() + ",snmp版本=" + version + ",团体名=" + community + ", 携带的变量=" + event.getPDU().getVariableBindings() + "]"); } public static void main(String[] args) { SnmpTrapHandler handler = new SnmpTrapHandler(); handler.start(); } }
测试如下:
来源: http://www.cnblogs.com/yuanfy008/p/8046189.html