概述
在网络编程中,有时我们需要判断两台机器之间的连通性,或者说是一台机器到另一台机器的网络可达性。在系统层面的测试中,我们常常用Ping命令来做验证。尽管Java提供了比较丰富的网络编程类库(包括在应用层的基于URL的网络资源读取,基于TCP/IP层的Socket编程,以及一些辅助的类库),但是没有直接提供类似Ping命令来测试网络连通性的方法。本文将介绍如何通过Java已有的API,编程实现各种场景下两台机器之间的网络可达性判断。在下面的章节中,我们会使用Java网络编程的一些类库java.net.InetAddress和java.net.Socket,通过例子解释如何模拟Ping命令。
简单判断两台机器的可达性
一般情况下,我们仅仅需要判断从一台机器是否可以访问(Ping)到另一台机器,此时,可以简单的使用 Java 类库中 java.net.InetAddress 类来实现,这个类提供了两个方法探测远程机器是否可达
- booleanisReachable(inttimeout)//测试地址是否可达
- booleanisReachable(NetworkInterfacenetif,intttl,inttimeout)
- //测试地址是否可达.
简单说来,上述方法就是通过远端机器的IP地址构造InetAddress对象,然后调用其isReachable方法,测试调用机器和远端机器的网络可达性。注意到远端机器可能有多个IP地址,因而可能要迭代的测试所有的情况。
清单1:简单判断两台机器的可达性
- voidisAddressAvailable(Stringip){
- try{
- InetAddressaddress=InetAddress.getByName(ip);//pingthisIP
- if(addressinstanceofjava.net.Inet4Address){
- System.out.println(ip+"isipv4address");
- }else
- if(addressinstanceofjava.net.Inet6Address){
- System.out.println(ip+"isipv6address");
- }else{
- System.out.println(ip+"isunrecongized");
- }
- if(address.isReachable(5000)){
- System.out.println("SUCCESS-ping"+IP+"withnointerfacespecified");
- }else{
- System.out.println("FAILURE-ping"+IP+"withnointerfacespecified");
- }
- System.out.println("\n-------Tryingdifferentinterfaces--------\n");
- Enumeration<NetworkInterface>netInterfaces=
- NetworkInterface.getNetworkInterfaces();
- while(netInterfaces.hasMoreElements()){
- NetworkInterfaceni=netInterfaces.nextElement();
- System.out.println(
- "Checkinginterface,DisplayName:"+ni.getDisplayName()+",Name:"+ni.getName());
- if(address.isReachable(ni,0,5000)){
- System.out.println("SUCCESS-ping"+ip);
- }else{
- System.out.println("FAILURE-ping"+ip);
- }
- Enumeration<InetAddress>ips=ni.getInetAddresses();
- while(ips.hasMoreElements()){
- System.out.println("IP:"+ips.nextElement().getHostAddress());
- }
- System.out.println("-------------------------------------------");
- }
- }catch(Exceptione){
- System.out.println("erroroccurs.");
- e.printStackTrace();
- }
- }
程序输出
- --------------START--------------
- 10.13.20.70isipv4address
- SUCCESS-ping10.13.20.70withnointerfacespecified
- -------Tryingdifferentinterfaces--------
- Checkinginterface,DisplayName:MSTCPLoopbackinterface,Name:lo
- FAILURE-ping10.13.20.70
- IP:127.0.0.1
- -------------------------------------------
- Checkinginterface,DisplayName:Intel(R)Centrino(R)Advanced-N6200AGN-
- Teefer2Miniport,Name:eth0
- FAILURE-ping10.13.20.70
- IP:9.123.231.40
- -------------------------------------------
- Checkinginterface,DisplayName:Intel(R)82577LMGigabitNetworkConnection-
- Teefer2Miniport,Name:eth1
- SUCCESS-ping10.13.20.70
- -------------------------------------------
- Checkinginterface,DisplayName:WAN(PPP/SLIP)Interface,Name:ppp0
- SUCCESS-ping10.13.20.70
- IP:10.0.50.189
- -------------------------------------------
- --------------END--------------
从上可以看出isReachable的用法,可以不指定任何接口来判断远端网络的可达性,但这不能区分出数据包是从那个网络接口发出去的 (如果本地有多个网络接口的话);而高级版本的isReachable则可以指定从本地的哪个网络接口测试,这样可以准确的知道远端网络可以连通本地的哪个网络接口。
但是,Java本身没有提供任何方法来判断本地的哪个IP地址可以连通远端网络,Java网络编程接口也没有提供方法来访问ICMP协议数据包,因而通过ICMP的网络不可达数据包实现这一点也是不可能的 (当然可以用JNI来实现,但就和系统平台相关了 ), 此时可以考虑本文下一节提出的方法。
指定本地和远程网络地址,判断两台机器之间的可达性
在某些情况下,我们可能要确定本地的哪个网络地址可以连通远程网络,以便远程网络可以回连到本地使用某些服务或发出某些通知。一个典型的应用场景是,本地启动了文件传输服务(如FTP),需要将本地的某个IP地址发送到远端机器,以便远端机器可以通过该地址下载文件;或者远端机器提供某些服务,在某些事件发生时通知注册了获取这些事件的机器 ( 常见于系统管理领域 ),因而在注册时需要提供本地的某个可达 (从远端) 地址。
虽然我们可以用InetAddress.isReachabl方法判断出本地的哪个网络接口可连通远程玩过,但是由于单个网络接口是可以配置多个IP地址的,因而在此并不合适。我们可以使用Socket建立可能的TCP连接,进而判断某个本地 IP 地址是否可达远程网络。我们使用java.net.Socket 类中的connect方法。
- voidconnect(SocketAddressendpoint,inttimeout)//使用Socket连接服务器,指定超时的时间
这种方法需要远程的某个端口,该端口可以是任何基于TCP协议的开放服务的端口(如一般都会开放的ECHO服务端口7,Linux的SSH服务端口22等)。实际上,建立的TCP连接被协议栈放置在连接队列,进而分发到真正处理数据的各个应用服务,由于UDP没有连接的过程,因而基于UDP的服务(如 SNMP)无法在此方法中应用。
具体过程是,枚举本地的每个网络地址,建立本地Socket,在某个端口上尝试连接远程地址,如果可以连接上,则说明该本地地址可达远程网络。
程序清单2:指定本地地址和远程地址,判断两台机器之间的可达性
- voidprintReachableIP(InetAddressremoteAddr,intport){
- StringretIP=null;
- Enumeration<NetworkInterface>netInterfaces;
- try{
- netInterfaces=NetworkInterface.getNetworkInterfaces();
- while(netInterfaces.hasMoreElements()){
- NetworkInterfaceni=netInterfaces.nextElement();
- Enumeration<InetAddress>localAddrs=ni.getInetAddresses();
- while(localAddrs.hasMoreElements()){
- InetAddresslocalAddr=localAddrs.nextElement();
- if(isReachable(localAddr,remoteAddr,port,5000)){
- retIP=localAddr.getHostAddress();
- break;
- }
- }
- }
- }catch(SocketExceptione){
- System.out.println(
- "Erroroccurredwhilelistingallthelocalnetworkaddresses.");
- }
- if(retIP==null){
- System.out.println("NULLreachablelocalIPisfound!");
- }else{
- System.out.println("ReachablelocalIPisfound,itis"+retIP);
- }
- }
- booleanisReachable(InetAddresslocalInetAddr,InetAddressremoteInetAddr,
- intport,inttimeout){
- booleanisReachable=false;
- Socketsocket=null;
- try{
- socket=newSocket();
- //端口号设置为0表示在本地挑选一个可用端口进行连接
- SocketAddresslocalSocketAddr=newInetSocketAddress(localInetAddr,0);
- socket.bind(localSocketAddr);
- InetSocketAddressendpointSocketAddr=
- newInetSocketAddress(remoteInetAddr,port);
- socket.connect(endpointSocketAddr,timeout);
- System.out.println("SUCCESS-connectionestablished!Local:"+
- localInetAddr.getHostAddress()+"remote:"+
- remoteInetAddr.getHostAddress()+"port"+port);
- isReachable=true;
- }catch(IOExceptione){
- System.out.println("FAILRE-CANnotconnect!Local:"+
- localInetAddr.getHostAddress()+"remote:"+
- remoteInetAddr.getHostAddress()+"port"+port);
- }finally{
- if(socket!=null){
- try{
- socket.close();
- }catch(IOExceptione){
- System.out.println("Erroroccurredwhileclosingsocket..");
- }
- }
- }
- returnisReachable;
- }
运行结果
- --------------START--------------
- FAILRE-CANnotconnect!Local:127.0.0.1remote:10.8.1.50port22
- FAILRE-CANnotconnect!Local:9.123.231.40remote:10.8.1.50port22
- SUCCESS-connectionestablished!Local:10.0.50.189remote:10.8.1.50port22
- ReachablelocalIPisfound,itis10.0.50.189
- --------------END--------------
IPv4和IPv6混合网络下编程
当网络环境中存在IPv4和IPv6,即机器既有IPv4地址,又有IPv6地址的时候,我们可以对程序进行一些优化,比如
程序清单3: 判断本地地址和远程地址是否同为IPv4或者IPv6
- //判断是IPv4还是IPv6
- if(!((localInetAddrinstanceofInet4Address)&&(remoteInetAddrinstanceofInet4Address)
- ||(localInetAddrinstanceofInet6Address)&&(remoteInetAddrinstanceofInet6Address))){
- //本地和远程不是同时是IPv4或者IPv6,跳过这种情况,不作检测
- break;
- }
程序清单4:跳过本地地址和LinkLocal地址
- if(localAddr.isLoopbackAddress()||
- localAddr.isAnyLocalAddress()||
- localAddr.isLinkLocalAddress()){
- //地址为本地环回地址,跳过
- break;
- }
总结和展望
本文列举集中典型的场景,介绍了通过Java网络编程接口判断机器之间可达性的几种方式。在实际应用中,可以根据不同的需要选择相应的方法稍加修改即可。对于更加特殊的需求,还可以考虑通过JNI的方法直接调用系统API来实现,能提供更加强大和灵活的功能,这里就不再赘述了。