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接口。
内部是如何工作
下圖是ResponseEvent response = snmp.send(requestPDU, target)内部實作簡要時序圖;
①建立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;