为什么InetAddress.isReachable返回false,而我可以ping通IP地址?

108
InetAddress byName = InetAddress.getByName("173.39.161.140");
System.out.println(byName);
System.out.println(byName.isReachable(1000));

当我可以ping通IP时,为什么isReachable返回false


可能是重复问题:https://dev59.com/C2445IYBdhLWcg3wmLhe - assylias
1
这个问题很相似,但我找不到解决方法的线索。所以我在这里重新提出了它。感谢您的提醒! - jiafu
2
我会尝试增加超时时间。 - jayunit100
这是一个非常好的问题,点赞数不够。我找到的唯一相似的问题被标记为Clojure问题,并且答案并不确定。 - jayunit100
6
有人能告诉我Reachable()到底是做什么的吗?即使在本地主机上它也返回false... - Seth Keno
12个回答

75
“isReachable”方法在许多情况下对我来说并不值得使用。您可以滚动到底部查看我用于简单测试您是否在线并能够解析外部主机(例如google.com)的替代方法...这通常在*NIX机器上运行良好。
问题:
关于这个问题有很多讨论:
以下是其他类似的问题:
- [Detect internet Connection using Java](link1) - [How do I test the availability of the internet in Java?](link2)
甚至还有一个关于同样问题的已报告的错误:
[https://bugs.java.com/bugdatabase/view_bug?bug_id=4921816](link3)
第一部分:问题的可重现示例
请注意,在这种情况下,它失败了。
       //also, this fails for an invalid address, like "www.sjdosgoogle.com1234sd" 
       InetAddress[] addresses = InetAddress.getAllByName("www.google.com");
      for (InetAddress address : addresses) {
        if (address.isReachable(10000))
        {   
           System.out.println("Connected "+ address);
        }
        else
        {
           System.out.println("Failed "+address);
        }
      }
          //output:*Failed www.google.com/74.125.227.114*

第二部分:一个巧妙的解决办法
作为另一种选择,你可以这样做:
// in case of Linux change the 'n' to 'c'
    Process p1 = java.lang.Runtime.getRuntime().exec("ping -n 1 www.google.com");
    int returnVal = p1.waitFor();
    boolean reachable = (returnVal==0);

Ping的-c选项将使ping尝试一次连接服务器(与我们在终端上使用的无限ping相反)。

如果主机可达,将返回0。否则,将返回"2"。

这样做更简单,但当然它是特定于平台的。 使用此命令可能会有某些特权限制,但我发现它在我的机器上有效。


*请注意:*
1. 这个解决方案并不是生产级别的,有点像是一个小技巧。如果谷歌不可用,或者你的网络暂时变慢,甚至可能是你的权限或系统设置出现问题,它可能会返回错误的结果(即使输入的地址是可达的)。
2. isReachable 失败是一个未解决的问题。再次强调 - 在撰写本文时,有几个在线资源表明目前没有一种“完美”的方法来实现这一点,这是由于 JVM 尝试连接主机的方式 - 我猜这是一个本质上与平台相关的任务,虽然简单,但 JVM 还没有足够地将其抽象化。

@jayunit100,两种方法都不起作用,使用 isReachable 我得到了失败的结果,使用 ping 我得到了 icmp 不允许的错误信息。你知道现在该怎么处理吗? - Yuvi
6
如果您使用的是Windows系统,则使用的标志与之前不同。您需要使用-n标志,而不是-c。 - James T Snell
waitFor()需要10秒才能返回。在Java 7中有没有一种方法可以提及超时? - Jaydev
@Jaydev,还有一个重载选项:public boolean waitFor(long timeout, TimeUnit unit)在java.lang.Process中(@since 1.8)。 - indivisible
截至2020年1月,我可以无任何问题地运行该问题的示例,它会报告一个IPv4地址和一个IPv6地址的“已连接”。错误在Java 5.0 b22中也得到了修复。也许现在的isReachable更加可靠。 - Lii
1
这不是问题的答案。 - Paula Livingstone

74

我来这里是为了得到对于同样问题的答案,但我对所有的答案都不满意,因为我正在寻找一种跨平台的解决方案。这里是我编写的代码,它是跨平台的,但需要关于其他计算机上任何开放端口的信息(我们大多数时候都有这些信息)。

private static boolean isReachable(String addr, int openPort, int timeOutMillis) {
    // Any Open port on other machine
    // openPort =  22 - ssh, 80 or 443 - webserver, 25 - mailserver etc.
    try {
        try (Socket soc = new Socket()) {
            soc.connect(new InetSocketAddress(addr, openPort), timeOutMillis);
        }
        return true;
    } catch (IOException ex) {
        return false;
    }
}

更新:根据最近在此答案下的评论,这是以上代码的简明版本:

private static boolean isReachable(String addr, int openPort, int timeOutMillis) {
    // Any Open port on other machine
    // openPort =  22 - ssh, 80 or 443 - webserver, 25 - mailserver etc.
    try (Socket soc = new Socket()) {
        soc.connect(new InetSocketAddress(addr, openPort), timeOutMillis);
        return true;
    } catch (IOException ex) {
        return false;
    }
}

3
这是一个很棒的跨平台解决方案,谢谢! - ivandov
1
我很高兴能来到这里,你的思维方式很棒,Sourabh :) - JustADev
1
这与 InetAddress.isReachable() 已经通过端口 7 做的完全相同,只是后者更加智能化地处理各种可能的 IOExceptions 对可达性的含义。 - user207421
1
太好了,谢谢!虽然我认为单个 try 块就足够了,对吧?只需将 "withResources" 部分移动到外部的 try 块中并删除内部的块即可。 - tomorrow
1
@tomorrow 你说得对!我会看看能否立即更新它。 - Sourabh Bhat
显示剩余7条评论

7

如果你只想检查互联网连接是否正常,请使用以下方法。该方法将返回true,以表示互联网已连接。最好使用程序尝试连接的站点地址。

     public static boolean isInternetReachable()
    {
        try {
            //make a URL to a known source
            URL url = new URL("http://www.google.com");

            //open a connection to that source
            HttpURLConnection urlConnect = (HttpURLConnection)url.openConnection();

            //trying to retrieve data from the source. If there
            //is no connection, this line will fail
            Object objData = urlConnect.getContent();

        } catch (Exception e) {              
            e.printStackTrace();
            return false;
        }

        return true;
    }

3
如果服务器没有Web服务器,那么这个东西就没用了,但这并不是问题中规定的条件。 - Fran Marzoa

4

我不确定在2012年提出问题时的情况。

现在,ping将作为root执行。通过ping可执行文件的授权,您将看到+s标志,并且属于root的进程,这意味着它将以root身份运行。在ping所在位置运行ls -liat,您应该会看到它。

因此,如果您以root身份运行InetAddress.getByName(“www.google.com”)。isReacheable(5000),它应该返回true。

您需要适当的授权来使用原始套接字,ICMP(ping使用的协议)使用原始套接字。

InetAddress.getByName与ping一样可靠,但是您需要在进程上拥有适当的权限才能使其正常运行。


4

其他答案没有明确提到,因此我特别提一下。在Unix系统中,isReachable()的ping部分需要root权限访问。正如bestsss在4779367中指出的那样:

如果你问为什么从bash运行ping不需要,实际上也是需要的。执行ls -l /bin/ping即可。

由于在我的情况下使用root权限不是一个选项,所以解决方案是允许防火墙访问特定服务器的端口7。


1
我建议唯一可靠的测试互联网连接的方法是实际连接并下载文件,或通过exec()解析OS ping调用的输出。您不能依赖ping的退出代码,而isReachable()则不可靠。
您不能依赖ping的退出代码,因为如果ping命令执行正确,则返回0。不幸的是,如果无法到达目标主机但从家庭ADSL路由器收到“目标主机不可达”的回复,则ping会执行正确。这是一种被视为成功命中的回复,因此退出代码= 0。不过需要补充说明的是,这是在Windows系统上的情况。*nixes未经检查。

0

InetAddress.isReachable 是 flappy 的,有时会返回无法访问的地址,但我们可以 ping。

我尝试了以下操作:

ping -c 1 <fqdn> 并检查 exit status

对于我尝试过并且 InetAddress.isReachable 不起作用的所有情况都有效。


0

既然您可以ping通计算机,那么您的Java进程应该具有足够的特权来执行检查。可能是由于使用了较低范围的端口。如果您使用sudo/superuser运行Java程序,我敢打赌它会起作用。


您不需要特权才能连接低端口。 - user207421

0

因为isReachable使用的是TCP协议(通过WireShark),而Ping命令使用的是ICMP协议,如果你想返回true,你需要打开7号端口。


1
Android的实现首先尝试ICMP ECHO REQUESTs,如果失败,则会退回到TCP ECHO REQUESTs。任何一种协议的成功都将返回true。 - hyena

0

检查互联网

public boolean isInternetAvailable() {
        try {
            InetAddress ipAddr = InetAddress.getByName("google.com");
            //You can replace it with your name
            return !ipAddr.equals("");

        } catch (Exception e1) {
            try {
                Process p1 = java.lang.Runtime.getRuntime().exec("/system/bin/ping  -W 1 -c 1 www.google.com");
                int returnVal = 0;
                returnVal = p1.waitFor();
                boolean reachable = (returnVal==0);
                return reachable;
            } catch (Exception e2) {
                e2.printStackTrace();
                return false;
            }
        }
    }

检查网络连接

private boolean isNetworkConnected() {
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

        return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接