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;