资源描述
用JAVA和SNMP实现流量统计(二)
计算流量统计的思路是:采集两次设备数据,用流量值的差值,除以时间的差值,即是当前的流量值,时间间隔我用的是5秒。这里需要注意的是,时间间隔不能太长。我们可以计算一下:
2^32*2^3/(1024*1024*1024)=32 OID采集的是字节,要换成bit
也就是说,OID值最多累积到32Gbit就会归零。对于一般的千兆级的网络设备来说,5秒的时间,最多也就累积5~6.25Gbit的流量,但对于万兆级的设备来说,5秒的时间,可能累积超过50Gbit的流量,这将导致OID值发生一次或两次归零,这就情况下,就要缩短间隔时间了,3秒就更适合一些。其实采集数据的间隔时间越短,就越容易采集到峰值,时间越长,计算出来的数据就越平均。由于我们还没有哪个设备的端口流量能在5秒内超过32Gbit,选5秒做为时间间隔还是很适合的。采集OID值的代码如下:
import java.io.IOException;
import .InetAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Vector;
import org.snmp4j.AbstractTarget;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
//统计流量的类,取时间与流量
public class Flow {
final String OCTER_STRING = "admin";// 共同体
final String TIME_OID = ".1.3.6.1.2.1.1.3.0";// 时间OID,所有设备一样
String IpAddress;// 设备IP地址
ArrayList<String> FlowOidGroup;// 流量OID,可能有多个
private String NowTime;// 端口流量的采集时间
private long FlowValue;// 端口流量的值
public boolean isSuccess = true;
// 构造器:IP地址和流量OID组(因为可能需要多个端口的流量加在一起)
public Flow(String IpAddress, ArrayList<String> FlowOidGroup) {
this.IpAddress = IpAddress;
this.FlowOidGroup = FlowOidGroup;
this.calc();
}
// 取当前时间
public String getNowTime() {
return NowTime;
}
// 取端口流量
public long getFlowValue() {
return FlowValue;
}
// 计算端口流量
@SuppressWarnings("unchecked")
private void calc() {
try {
Snmp snmp;
TransportMapping transport;
Address targetAddress = GenericAddress.parse("udp:" + IpAddress
+ "/161");
transport = new DefaultUdpTransportMapping();
snmp = new Snmp(transport);
transport.listen();// 监听
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(OCTER_STRING));// 设置共同体名
target.setAddress(targetAddress);// 设置目标Agent地址
target.setRetries(2);// 重试次数
target.setTimeout(3000);// 超时设置
target.setVersion(1);// 版本
PDU request = new PDU();
request.setType(PDU.GET);// 操作类型GET
request.add(new VariableBinding(new OID(TIME_OID)));// OID_sysUpTime
for (String FlowOid : FlowOidGroup) {
request.add(new VariableBinding(new OID(FlowOid)));
}
// 取两次数据,间隔10秒,算差值
long[] time = new long[2];
long[][] flow = new long[2][FlowOidGroup.size()];
for (int count = 0; count < 2; count++) {
ResponseEvent respEvt = snmp.send(request, target);// 发送请求
if (respEvt != null && respEvt.getResponse() != null) {
// 从目的设备取值,得到Vector
Vector<VariableBinding> revBindings = (Vector<VariableBinding>) respEvt.getResponse().getVariableBindings();
String TimeTicks = revBindings.elementAt(0)
.getVariable().toString().trim();
String[] TimeString = TimeTicks.split(" ");// 得到时间字符串数组
// 取时间 186 days, 21:26:15.24,也有可能没有day,就是不到一天
if(TimeTicks.contains("day")){
time[count] = Long.parseLong(TimeString[0])
* 24
* 3600
+ Long.parseLong(TimeString[2].split(":")[0])
* 3600
+ Long.parseLong(TimeString[2].split(":")[1])
* 60
+ Math.round(Double.parseDouble(TimeString[2]
.split(":")[2]));
}else{
time[count] = Long.parseLong(TimeString[0].split(":")[0])
* 3600
+ Long.parseLong(TimeString[0].split(":")[1])
* 60
+ Math.round(Double.parseDouble(TimeString[0]
.split(":")[2]));
}
// 取端口流量
for (int i = 0; i < FlowOidGroup.size(); i++) {
flow[count][i] = Long.parseLong(revBindings
.elementAt(i + 1).getVariable().toString());
}
isSuccess = true;
} else {
isSuccess = false;
}
if (count == 0)
Thread.sleep(5000);// 延时5秒后,第二次取值
}
snmp.close();
transport.close();
// 计算并为时间和最终流量赋值
Calendar c = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
NowTime = sdf.format(c.getTime());// 当前时间
long AllSubValue = 0;
for (int i = 0; i < FlowOidGroup.size(); i++) {
long sub = flow[1][i] - flow[0][i];
/*
* 端口流量值为无符号32位,超出后就归0,所以如果两次取值差值为负,
* 必然出现一次归0的情况,由于单个端口的流量不可能超过每5秒1*2^32字节
*/
if (sub < 0) {
// 因为端口流量为无符号32位,所以最大值是有符号32位的两倍
sub += 2L * Integer.MAX_VALUE;
}
AllSubValue += sub;
}
if (time[1] - time[0] != 0) {
// 字节换算成兆比特才是最终流量
FlowValue = (long)(AllSubValue / 1024.0 / 1024 * 8 / (time[1] - time[0]));isSuccess = true;
} else {
System.out.println("地址:" + IpAddress + "数据采集失败!");
isSuccess = false;
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
程序的完善
以上只是单个设备的一次流量采集,接下来编写代码读取之前写好的配置文件,并根据文件内容生成Flow对象,再将采集到的数据插入到数据库,采集部分的工作就完成了。我写的代码采集周期是5分钟,与CACTI一样,共采集40多个设备。
可以写成控制台程序,也可以写成界面方式,用以显示设备的连接状态。
控制台程序如下:
界面程序如下:
还可以把界面程序隐藏到右下角:
报表程序
报表程序可以写成C/S架构,也可以写成B/S架构,它们各自的优势就不用说了。
用Delphi写的曲线图表
用Java EE+JBoss+JFreeChart写的报表和图表
PS:由于本人对Java EE的学习尚浅,所以Web程序的开发是我的一个同事个人完成的,他可是专业的。
至此,一个流量统计系统就完成了。在这个系统程序编写的过程中,我学到了很多知识,也思考并解决了不少以前不曾想过或遇过的问题。作为一名业余的编程爱好者,用自己的编程知识解决工作中的实际问题,不但更增加了自己的学习动力,也为自己和同事节省出更多的时间,真是一件让人快乐的事!
展开阅读全文