java测试网络连通性

摘要:本文列举集中典型的场景,介绍了通过Java网络编程接口判断机器之间可达性的几种方式。在实际应用中,可以根据不同的需要选择相应的方法稍加修改即可。对于更加特殊的需求,还可以考虑通过JNI的方法直接调用系统API来实现,能提供更加强大和灵活的功能。

概述

在网络编程中,有时我们需要判断两台机器之间的连通性,或者说是一台机器到另一台机器的网络可达性。在系统层面的测试中,我们常常用Ping命令来做验证。尽管Java提供了比较丰富的网络编程类库(包括在应用层的基于URL的网络资源读取,基于TCP/IP层的Socket编程,以及一些辅助的类库),但是没有直接提供类似Ping命令来测试网络连通性的方法。本文将介绍如何通过Java已有的API,编程实现各种场景下两台机器之间的网络可达性判断。在下面的章节中,我们会使用Java网络编程的一些类库java.net.InetAddress和java.net.Socket,通过例子解释如何模拟Ping命令。

简单判断两台机器的可达性

一般情况下,我们仅仅需要判断从一台机器是否可以访问(Ping)到另一台机器,此时,可以简单的使用 Java 类库中 java.net.InetAddress 类来实现,这个类提供了两个方法探测远程机器是否可达

  
  1. booleanisReachable(inttimeout)//测试地址是否可达
  2. booleanisReachable(NetworkInterfacenetif,intttl,inttimeout)
  3. //测试地址是否可达.

简单说来,上述方法就是通过远端机器的IP地址构造InetAddress对象,然后调用其isReachable方法,测试调用机器和远端机器的网络可达性。注意到远端机器可能有多个IP地址,因而可能要迭代的测试所有的情况。

清单1:简单判断两台机器的可达性

  
  1. voidisAddressAvailable(Stringip){
  2. try{
  3. InetAddressaddress=InetAddress.getByName(ip);//pingthisIP
  4. if(addressinstanceofjava.net.Inet4Address){
  5. System.out.println(ip+"isipv4address");
  6. }else
  7. if(addressinstanceofjava.net.Inet6Address){
  8. System.out.println(ip+"isipv6address");
  9. }else{
  10. System.out.println(ip+"isunrecongized");
  11. }
  12. if(address.isReachable(5000)){
  13. System.out.println("SUCCESS-ping"+IP+"withnointerfacespecified");
  14. }else{
  15. System.out.println("FAILURE-ping"+IP+"withnointerfacespecified");
  16. }
  17. System.out.println("\n-------Tryingdifferentinterfaces--------\n");
  18. Enumeration<NetworkInterface>netInterfaces=
  19. NetworkInterface.getNetworkInterfaces();
  20. while(netInterfaces.hasMoreElements()){
  21. NetworkInterfaceni=netInterfaces.nextElement();
  22. System.out.println(
  23. "Checkinginterface,DisplayName:"+ni.getDisplayName()+",Name:"+ni.getName());
  24. if(address.isReachable(ni,0,5000)){
  25. System.out.println("SUCCESS-ping"+ip);
  26. }else{
  27. System.out.println("FAILURE-ping"+ip);
  28. }
  29. Enumeration<InetAddress>ips=ni.getInetAddresses();
  30. while(ips.hasMoreElements()){
  31. System.out.println("IP:"+ips.nextElement().getHostAddress());
  32. }
  33. System.out.println("-------------------------------------------");
  34. }
  35. }catch(Exceptione){
  36. System.out.println("erroroccurs.");
  37. e.printStackTrace();
  38. }
  39. }

程序输出

  
  1. --------------START--------------
  2. 10.13.20.70isipv4address
  3. SUCCESS-ping10.13.20.70withnointerfacespecified
  4. -------Tryingdifferentinterfaces--------
  5. Checkinginterface,DisplayName:MSTCPLoopbackinterface,Name:lo
  6. FAILURE-ping10.13.20.70
  7. IP:127.0.0.1
  8. -------------------------------------------
  9. Checkinginterface,DisplayName:Intel(R)Centrino(R)Advanced-N6200AGN-
  10. Teefer2Miniport,Name:eth0
  11. FAILURE-ping10.13.20.70
  12. IP:9.123.231.40
  13. -------------------------------------------
  14. Checkinginterface,DisplayName:Intel(R)82577LMGigabitNetworkConnection-
  15. Teefer2Miniport,Name:eth1
  16. SUCCESS-ping10.13.20.70
  17. -------------------------------------------
  18. Checkinginterface,DisplayName:WAN(PPP/SLIP)Interface,Name:ppp0
  19. SUCCESS-ping10.13.20.70
  20. IP:10.0.50.189
  21. -------------------------------------------
  22. --------------END--------------

从上可以看出isReachable的用法,可以不指定任何接口来判断远端网络的可达性,但这不能区分出数据包是从那个网络接口发出去的 (如果本地有多个网络接口的话);而高级版本的isReachable则可以指定从本地的哪个网络接口测试,这样可以准确的知道远端网络可以连通本地的哪个网络接口。

但是,Java本身没有提供任何方法来判断本地的哪个IP地址可以连通远端网络,Java网络编程接口也没有提供方法来访问ICMP协议数据包,因而通过ICMP的网络不可达数据包实现这一点也是不可能的 (当然可以用JNI来实现,但就和系统平台相关了 ), 此时可以考虑本文下一节提出的方法。

指定本地和远程网络地址,判断两台机器之间的可达性

在某些情况下,我们可能要确定本地的哪个网络地址可以连通远程网络,以便远程网络可以回连到本地使用某些服务或发出某些通知。一个典型的应用场景是,本地启动了文件传输服务(如FTP),需要将本地的某个IP地址发送到远端机器,以便远端机器可以通过该地址下载文件;或者远端机器提供某些服务,在某些事件发生时通知注册了获取这些事件的机器 ( 常见于系统管理领域 ),因而在注册时需要提供本地的某个可达 (从远端) 地址。

虽然我们可以用InetAddress.isReachabl方法判断出本地的哪个网络接口可连通远程玩过,但是由于单个网络接口是可以配置多个IP地址的,因而在此并不合适。我们可以使用Socket建立可能的TCP连接,进而判断某个本地 IP 地址是否可达远程网络。我们使用java.net.Socket 类中的connect方法。

  
  1. voidconnect(SocketAddressendpoint,inttimeout)//使用Socket连接服务器,指定超时的时间

这种方法需要远程的某个端口,该端口可以是任何基于TCP协议的开放服务的端口(如一般都会开放的ECHO服务端口7,Linux的SSH服务端口22等)。实际上,建立的TCP连接被协议栈放置在连接队列,进而分发到真正处理数据的各个应用服务,由于UDP没有连接的过程,因而基于UDP的服务(如 SNMP)无法在此方法中应用。

具体过程是,枚举本地的每个网络地址,建立本地Socket,在某个端口上尝试连接远程地址,如果可以连接上,则说明该本地地址可达远程网络。

程序清单2:指定本地地址和远程地址,判断两台机器之间的可达性

  
  1. voidprintReachableIP(InetAddressremoteAddr,intport){
  2. StringretIP=null;
  3. Enumeration<NetworkInterface>netInterfaces;
  4. try{
  5. netInterfaces=NetworkInterface.getNetworkInterfaces();
  6. while(netInterfaces.hasMoreElements()){
  7. NetworkInterfaceni=netInterfaces.nextElement();
  8. Enumeration<InetAddress>localAddrs=ni.getInetAddresses();
  9. while(localAddrs.hasMoreElements()){
  10. InetAddresslocalAddr=localAddrs.nextElement();
  11. if(isReachable(localAddr,remoteAddr,port,5000)){
  12. retIP=localAddr.getHostAddress();
  13. break;
  14. }
  15. }
  16. }
  17. }catch(SocketExceptione){
  18. System.out.println(
  19. "Erroroccurredwhilelistingallthelocalnetworkaddresses.");
  20. }
  21. if(retIP==null){
  22. System.out.println("NULLreachablelocalIPisfound!");
  23. }else{
  24. System.out.println("ReachablelocalIPisfound,itis"+retIP);
  25. }
  26. }
  27. booleanisReachable(InetAddresslocalInetAddr,InetAddressremoteInetAddr,
  28. intport,inttimeout){
  29. booleanisReachable=false;
  30. Socketsocket=null;
  31. try{
  32. socket=newSocket();
  33. //端口号设置为0表示在本地挑选一个可用端口进行连接
  34. SocketAddresslocalSocketAddr=newInetSocketAddress(localInetAddr,0);
  35. socket.bind(localSocketAddr);
  36. InetSocketAddressendpointSocketAddr=
  37. newInetSocketAddress(remoteInetAddr,port);
  38. socket.connect(endpointSocketAddr,timeout);
  39. System.out.println("SUCCESS-connectionestablished!Local:"+
  40. localInetAddr.getHostAddress()+"remote:"+
  41. remoteInetAddr.getHostAddress()+"port"+port);
  42. isReachable=true;
  43. }catch(IOExceptione){
  44. System.out.println("FAILRE-CANnotconnect!Local:"+
  45. localInetAddr.getHostAddress()+"remote:"+
  46. remoteInetAddr.getHostAddress()+"port"+port);
  47. }finally{
  48. if(socket!=null){
  49. try{
  50. socket.close();
  51. }catch(IOExceptione){
  52. System.out.println("Erroroccurredwhileclosingsocket..");
  53. }
  54. }
  55. }
  56. returnisReachable;
  57. }

运行结果

  
  1. --------------START--------------
  2. FAILRE-CANnotconnect!Local:127.0.0.1remote:10.8.1.50port22
  3. FAILRE-CANnotconnect!Local:9.123.231.40remote:10.8.1.50port22
  4. SUCCESS-connectionestablished!Local:10.0.50.189remote:10.8.1.50port22
  5. ReachablelocalIPisfound,itis10.0.50.189
  6. --------------END--------------

IPv4和IPv6混合网络下编程

当网络环境中存在IPv4和IPv6,即机器既有IPv4地址,又有IPv6地址的时候,我们可以对程序进行一些优化,比如

  • 由于IPv4和IPv6地址之间是无法互相访问的,因此仅需要判断IPv4地址之间和IPv6地址之间的可达性。
  • 对于IPv4的换回地址可以不做判断,对于IPv6的Linklocal地址也可以跳过测试
  • 根据实际的需要,我们可以优先考虑选择使用IPv4或者IPv6,提高判断的效率

程序清单3: 判断本地地址和远程地址是否同为IPv4或者IPv6

  
  1. //判断是IPv4还是IPv6
  2. if(!((localInetAddrinstanceofInet4Address)&&(remoteInetAddrinstanceofInet4Address)
  3. ||(localInetAddrinstanceofInet6Address)&&(remoteInetAddrinstanceofInet6Address))){
  4. //本地和远程不是同时是IPv4或者IPv6,跳过这种情况,不作检测
  5. break;
  6. }

程序清单4:跳过本地地址和LinkLocal地址

  
  1. if(localAddr.isLoopbackAddress()||
  2. localAddr.isAnyLocalAddress()||
  3. localAddr.isLinkLocalAddress()){
  4. //地址为本地环回地址,跳过
  5. break;
  6. }

总结和展望

本文列举集中典型的场景,介绍了通过Java网络编程接口判断机器之间可达性的几种方式。在实际应用中,可以根据不同的需要选择相应的方法稍加修改即可。对于更加特殊的需求,还可以考虑通过JNI的方法直接调用系统API来实现,能提供更加强大和灵活的功能,这里就不再赘述了。

你可能感兴趣的:(java)