JavaEye上不少朋友是做网管系统的。一个典型的网络管理系统,需要具备FCAPS几个标准模块,而网络的自动发现和拓扑展示是核心之一。很多人不喜欢Java的Swing,而本文就用一个很小很小的例子,来模拟一个小小的网络管理程序,希望能给大家一点启发。虽然很小,它却可以完成一个简单的局域网自动发现搜索、多线程、ICMP和SNMP的ping、节点的生成、拓扑的展示、自动布局等功能。继续改巴改巴也许还有点使用价值也未可知。
如果不喜欢研究代码,就当它是一个趣味程序吧!你可以在公司的网络里面搜索一把,把同事的机器都挖出来,看看你们公司的网络结构是怎样的;如果喜欢研究代码,可以看看相关SNMP、多线程和拓扑图展示的部分,虽然很简单,就当看肥皂剧消遣了。
Ping和SNMP PING
这个程序的自动发现比较简单,就是对所在的网段进行便利搜索。首先,获得本机的网址以及所在的网段。例如,如果本机的地址是192.168.1.122,那么所在的网段自然就是192.168.1.0。然后,将这个网段中所有可能存在的IP地址进行拆分,并通过多线程进行任务分配,一个一个的Ping。
public static boolean ping(String ip) {
try {
InetAddress ipaddress = InetAddress.getByName(ip);
return ipaddress.isReachable(2000);
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
用Java来Ping机器,有两个做法。一个是传统的调用命令行执行Ping命令的做法。这种做法的好处是速度快,比较可靠。缺点是,不同的操作系统,甚至Windows的不同版本,其执行和返回结果格式都可能不同,造成跨平台的不便以及代码的啰嗦。第二个方法自然就是使用大家都熟知的Java 5提供的InetAddress的isReachable方法。这个方法本来应当很好,可是在实际使用中就会发现,它不大灵光。超时时间设置短了吧,就ping不through;长了把,又贼慢。网上不少人都反映和抱怨这个问题。仔细研究这个isReachable,会发现更多的问题。1、它不是线程安全的。也就是说,为了提高速度而使用多线程进行多节点并行ping,会导致不安全的返回结果。这个问题挺致命。2、这个函数并非使用ICMP的ping,而是仅仅用TCP连一下7号端口而已:
又慢又线程不安全就比较不爽了。还可以使用上面提到的JPCAP这个库来完成。这个库的地址是:
http://netresearch.ics.uci.edu/kfujii/jpcap/doc/
不管怎么说,一个小小的ping还是挺麻烦。不过本例子由于仅仅是示例小程序,还是使用了isReachable方法,简化代码。
在ping通一个机器后,接下来再使用SNMP进行ping。做过SNMP网管的朋友知道,所谓SNMP Ping其实就是用SNMP去get一个非常基本的OID看对方有无反应。如果能够返回数据,说明这是一个SNMP节点,可以通过SNMP配合MIB库去获取更多的业务数据。例如磁盘、CPU、内存、端口力量等等基本的信息,都有相关的SNMP MIB进行定义。
这个例子使用了Westhawk's SNMP stack这个SNMP协议栈,一个轻量的、Java的、开源的、免费的SNMP协议栈,实现了SNMPv1、SNMPv2c以及SNMPv3 (包括MD5和SHA1以及DES, AES加密算法)。地址在这里:
使用Westhawk's SNMP做一个简单的get操作如下:
SnmpContextv2c context = new SnmpContextv2c(ip, 161);
context.setCommunity("public");
BlockPdu pdu = new BlockPdu(context);
pdu.setRetryIntervals(new int[]{1000});
String sysUpTime = "1.3.6.1.2.1.1.3.0";
pdu.addOid(sysUpTime);
Object result = pdu.getResponseVariable();
代码中用v2c,并假设community是public,超时时间1秒。获取sysUpTime也就是设备启动时间。如果有返回,认为节点存在且SNMP协议已启动。
本例子就ping这么多。如果做一个真正的综合设备网管,可以先获得设备的标识OID,判断其设备厂商和型号,然后加载对应设备支持的MIB进行复杂的监控。
多线程任务
由于一个网段需要ping的地址很多,一个线程会很慢。所以这个例子中使用很多线程并发进行。例如192.168.1.*里面有254个可能节点,就用10个线程去分头ping然后汇总。这个让人想起网络蚂蚁。于是就做了一个类似网络蚂蚁的界面。
其中,每个球是一个可能存在的节点地址。每个红色的球是一个线程正在ping这个节点。灰色的球是已经被ping过证明不存在或无法ping通的地址。绿色球是已经ping通,存在的节点。
通过调节线程的数量,可以掌握网络发现的速度。一般这254个节点,可以在30秒到60秒内完成。
拓扑呈现
拓扑呈现用TWaver就行了。每次发现一个存在的节点,往Network中new一个Node,设置一个图标即可。同时,在网段节点(一个云形图标的节点)和计算机节点创建一个连线。
同时,把拓扑图network组件的弹簧布局打开。这样,每次节点加入,都会像弹簧一样被自动布局到合适的位置,比较动感、有视觉效果。
network.getSpringLayouter().setMovableFilter(new MovableFilter() {
public boolean isMovable(Element element) {
return element != centerNode;
}
});
network.getSpringLayouter().start();
network.getSpringLayouter().setLinkRepulsionFactor(2);
另外,一旦ping通,我们在节点上就显示一个windows图标;如果snmp能ping通,再显示一个齿轮的图标。显示效果如下:
显示图标的代码很简单:
ResizableNode node = new ResizableNode(ipaddress);
node.setImage("/demo/main/snmp/images/node.png");
node.addAttachment("winxp");
node.putAttachmentPosition(TWaverConst.POSITION_TOPLEFT);
if (snmpPingOK) {
node.addAttachment("snmp");
}
此外,可以通过windows的“net view hostname”的命令来查看一个机器的共享信息。我们做一个右键菜单,将执行命令结果显示出来:
显示结果如下:
结果显示,这台test计算机上有“move”、“SharedDocs”两个共享目录,以及三个共享打印机。实现的代码如下:
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import twaver.*;
import twaver.network.*;
public class SnmpPopupMenuFactory implements PopupMenuFactory {
private TNetwork network = null;
public SnmpPopupMenuFactory(TNetwork network) {
this.network = network;
}
public JPopupMenu getPopupMenu(DataBoxSelectionModel dataBoxSelectionModel, Point point) {
if (network.getDataBox().getSelectionModel().size() == 1) {
Element element = network.getDataBox().getSelectionModel().lastElement();
if (element instanceof ResizableNode) {
final Node node = (Node) element;
JPopupMenu menu = new JPopupMenu();
JMenuItem item = new JMenuItem("View this computer");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String result = executeCommand("net view \\\\" + node.getName());
if (result != null && !result.trim().isEmpty()) {
JOptionPane.showMessageDialog(network, result);
} else {
JOptionPane.showMessageDialog(network, "No information available.");
}
}
});
menu.add(item);
return menu;
}
}
return null;
}
private static String executeCommand(String command) {
try {
Process p = Runtime.getRuntime().exec(command);
InputStreamReader ir = new InputStreamReader(p.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
String result = null;
String line = input.readLine();
while (line != null) {
if (result == null) {
result = line;
} else {
if (!line.trim().equalsIgnoreCase("")) {
result = result + "\n" + line.trim();
}
}
line = input.readLine();
}
return result;
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
//可以用这两行代码来测试test机器的返回结果。
public static void main(String[] args) {
String result = executeCommand("net view \\\\test");
System.out.println(result);
}
}
链路探测与告警
在所有的节点被探索结束并放入界面后,我们可以起一个线程,周期性对每个节点进行ping。一旦无法ping通,生成告警,显示在拓扑图中。
Thread linkCheckThread = new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(3000);
if (!network.getDataBox().isEmpty()) {
Collection elements = network.getDataBox().getAllElements();
Iterator it = elements.iterator();
while (it.hasNext()) {
final Element element = (Element) it.next();
if (element instanceof ResizableNode) {
final String ipaddress = element.getID().toString();
final boolean pingOK = ping(ipaddress);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Alarm alarm = new Alarm();
if (!pingOK) {
alarm.setAlarmSeverity(AlarmSeverity.CRITICAL);
} else {
if (element.getAlarmState().isEmpty()) {
return;
}
alarm.setAlarmSeverity(AlarmSeverity.CLEARED);
}
alarm.setElementID(ipaddress);
alarm.setProbableCause(AlarmProbableCause.LINE_INTERFACE_FAILURE);
box.getAlarmModel().addAlarm(alarm);
}
});
}
}
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
};
linkCheckThread.start();
}
将告警放置在一个告警表格中:
同时,让告警表和拓扑图共享一个DataBox,于是告警就会在拓扑中显示:
最终效果以及源代码下载
这是用这个小程序探索我们办公室的网络结构。你的呢?也可以发上来看看!
源代码、第三方lib包、可执行包、run.bat都在附件中,请大家自行下载。请确保安装了JAVA 6。解压后双击run.bat即可。在弹出的对话框中点击start按钮即可进行网络自动发现。
GOOD LUCK & HAVE FUN!