天天看點

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;​