天天看点

snmp4j学习笔记

snmp4j是什么

        SNMP4J 是一个用 Java 来实现 SNMP (简单网络管理协议) 协议的开源项目。

官网地址 ​​https://agentpp.com/api/java/snmp4j.html​​有详细介绍支持哪些特性,这里不再多说。

近期学习了一下snmp4j,下面通过简单举例(发送一个snmp同步消息)来记录一下snmp4j的使用、关键类、内部工作流程。

Snmp4j如何使用​

Snmp snmp = new Snmp(new DefaultUdpTransportMapping());
snmp.listen();
 
PDU requestPDU = new PDU();
requestPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1"))); // sysDescr
pdu.setType(PDU.GETNEXT);
 
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString("public"));
target.setAddress(targetAddress);
target.setVersion(SnmpConstants.version1);
 
ResponseEvent response = snmp.send(requestPDU, target);
if (response.getResponse() == null) {
 // request timed out
}
else {
 System.out.println("Received response from: "+
 response.getPeerAddress());
 // dump response PDU
 System.out.println(response.getResponse().toString());
}      

关键类

      从上面发送同步消息来自可以看出Snmp是关键类,下面围绕ResponseEvent response = snmp.send(requestPDU, target)语句,画出了内部工作锁涉及的关键类图。

Snmp类实现了CommandResponder接口;

MessageDispatcherImpl实现了MessageDispatcherImpl和TransportListener接口;

DefaultUdpTransportMapping类实现了TransportMapping接口;

PendingRequest类实现了TimerTask接口;

ListenThread是DefaultUdpTransportMapping的内部类,实现了Runable接口。

  1. snmp4j学习笔记

内部是如何工作​

下图是ResponseEvent response = snmp.send(requestPDU, target)内部实现简要时序图;

snmp4j学习笔记

①创建Snmp实例并关联DefaultUdpTransportMapping和MessageDespatcherImpl实例,然后开始监听接收数据流;这是发送消息前必须步骤,举例中是监听所有以太网口的所有端口号:​

Snmp snmp = new Snmp(new DefaultUdpTransportMapping());​
snmp.listen();​      

②send方法内创建了当前消息对应的PendingRequest请求对象,以及创建了SyncResponseListener同步消息应答监听器;​

final SyncResponseListener syncResponse = new SyncResponseListener();​
 PendingRequest retryRequest = null;​
 synchronized (syncResponse) {​
 PduHandle handle = null;​
 PendingRequest request =​
 new PendingRequest(syncResponse, target, pdu, target, transport);​
 request.maxRequestStatus = maxRequestStatus;​
 handle = sendMessage(request.pdu, target, transport, request);​
 try {​
 while ((syncResponse.getResponse() == null) &&​
 (System.nanoTime() < stopTime)) {​
 syncResponse.wait(totalTimeout);​
 }​
 retryRequest = pendingRequests.remove(handle);​
 request.setFinished();​
 request.cancel();​
 }​
 catch (InterruptedException iex) {​
 // cleanup request​
  ...​
 }​
 finally {​
 // free resources​
...​
 }​
 }​      

其中PendingRequest有个回调方法pduHandleAssigned,主要是在pendingRequests中放入了PduHandle和PendingRequest的键值对,其中PduHandle中存放了以太网请求包的requestId,以太网响应包中也有requestId用于标识响应哪个请求包,这样请求响应时就可匹配到对应的PendingRequest对象;回调方法中还有个定时器 timerCopy.schedule(this, delay),用于处理超时未响应时的重发。​

③MessageDispatcherImpl.sendPdu主要创建了②步中的PduHandle,以及调用DefaultUdpTransportMapping.SendMessage()方法;​

④DefaultUdpTransportMapping.SendMessage()方法主要是调用java.net.DatagramSocket.send()方法发送UDP报文;​

DatagramSocket s = ensureSocket();​
s.send(new DatagramPacket(message, message.length, targetSocketAddress));​      

到这里SNMP报文发送完成,下面是监听接收SNMP-Response报文,并匹配到PendingRequest对象并响应上面的Snmp.send同步方法;​

⑤ 在DefaultUdpTransportMapping.listen()中,实际是创建了一个线程WorkTask,任务是 DefaultUdpTransportMapping内部类ListenThread的对象;这个线程一直运行监听DatagramSocket.receive(),直到调用Snmp.close()释放资源时停止线程;​

public synchronized void listen() throws IOException {​
 if (listener != null) {​
 throw new SocketException("Port already listening");​
 }​
 ensureSocket();​
 listenerThread = new ListenThread();​
 listener = SNMP4JSettings.getThreadFactory().createWorkerThread(​
 "DefaultUDPTransportMapping_"+getAddress(), listenerThread, true);​
 listener.run();​
 }​      

⑥在ListenThread内DatagramSocket.receive()到数据流后,调用DefaultUdpTransportMapping.fireProcessMessage,然后调用TransportListener类型监听器MessageDipatcherImpl的processMessage方法,processMessage内再调用自己dispatchMessage方法;

⑦dispatchMessage()内先是通过MessageProcessingModel.prepareDataElements类解析数据流,其中MessageProcessingModel

就是不同版本协议模板(MPv1、MPv2c、MPv3),比如解析数据流中requestId; 然后构建一个CommandResponderEvent对象,再调用fireProcessPdu方​​​​​​法,fireProcessPdu()内再调用CommandResponder类型监听器Snmp的processPdu()方法;

protected void fireProcessPdu(CommandResponderEvent e) {​
 if (commandResponderListeners != null) {​
 List<CommandResponder> listeners = commandResponderListeners;​
 for (CommandResponder listener : listeners) {​
 listener.processPdu(e);​
 // if event is marked as processed the event is not forwarded to​
 // remaining listeners​
 if (e.isProcessed()) {​
 return;​
 }​
 }​
 }​
 }​      

⑧ Snmp的processPdu()内根据⑦中解析的PduHandle匹配到PendingRequest,然后调用PendingRequest内注册的Listener监听器(就是②中的SyncResponseListener).onResponse()方法;​

ResponseListener l = request.listener;​
 if (l != null) {​
 l.onResponse(new ResponseEvent(this,​
 event.getPeerAddress(),​
 request.pdu,​
 pdu,​
 request.userObject));​      

⑨ SyncResponseListener.onResponse()如下,调用了notify()通知了调用wait()的等待线程(②中syncResponse.wait);​

public synchronized void onResponse(ResponseEvent event) {​
 this.response = event;​
 this.notify();​
 }​      
retryRequest = pendingRequests.remove(handle);​
 if (logger.isDebugEnabled()) {​
 logger.debug("Removed pending request with handle: " + handle);​
 }​
 request.setFinished();​
 request.cancel();​
...​
if (syncResponse.getResponse() == null) {​
 syncResponse.response =​
 new ResponseEvent(Snmp.this, null, pdu, null, null);​
 }​
return syncResponse.response;​