1

遵循以下说明后会发生 java.net.UnknownHostException:

指示:

  1. 断开计算机与 Internet 的连接(例如:关闭调制解调器)
  2. 将操作系统日期更改为未来 14 天
  3. 运行下面的代码
  4. 单击“执行测试”按钮
  5. 您将收到一条异常消息,但这是正确的,因为互联网已断开连接
  6. 在消息窗口中单击确定
  7. 重新连接互联网
  8. 将操作系统日期更改回今天的日期
  9. 单击“执行测试”按钮
  10. 您将收到一条异常消息,但您不应该,因为互联网已连接
  11. 您可以一次又一次地单击“执行测试”按钮,尽管已连接到互联网,但总是会出现相同的异常。
  12. 如果您在浏览器上测试 URL ( http://test.com ),它将起作用

为什么会这样?我想这不应该发生。

简单代码:

公共类 TestUrlConnection{

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable(){
        @Override
        public void run(){
            createAndShowGui();
        }
    });
}

private static void createAndShowGui(){
    final JFrame frame = new JFrame("Test URL Connection");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JButton btOk = new JButton("Perform Test");     
    btOk.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent e){
            try{
                String content = readContentFromTestUrl();
                if(content!=null)
                    JOptionPane.showMessageDialog(frame, "OK. URL Connection returned Content.\nContent length = "+content.length());
                else
                    JOptionPane.showMessageDialog(frame, "Content is null, but no exception");
            }
            catch(IOException exc){
                JOptionPane.showMessageDialog(frame, "Exception:\n\n"+stackTraceToString(exc));
            }
        }
    });

    frame.setContentPane(btOk);     
    frame.setMinimumSize(new Dimension(250,100));
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
}   

private static String readContentFromTestUrl() throws IOException{
    InputStream is = null;
    InputStreamReader isr = null;
    BufferedReader br = null;

    try {
        URL url = new URL("http://test.com");
        URLConnection urlConnection = url.openConnection();
        urlConnection.setUseCaches(false);

        is = urlConnection.getInputStream();
        isr = new InputStreamReader(is);
        br = new BufferedReader(isr);

        String inputLine;
        String content = "";

        while ((inputLine = br.readLine()) != null)
            content += inputLine + "\n";

        return content;
    }
    finally{
        if(is!=null){try{is.close();}catch(IOException e){e.printStackTrace();}}
        if(isr!=null){try{isr.close();}catch(IOException e){e.printStackTrace();}}
        if(br!=null){try{br.close();}catch(IOException e){e.printStackTrace();}}
    }
}

private static String stackTraceToString(IOException exc){
    exc.printStackTrace();
    StringWriter sw = new StringWriter();
    exc.printStackTrace(new PrintWriter(sw));
    String s = sw.toString();
    return s.substring(0, s.length()>600 ? 600 : s.length());
}

}

4

1 回答 1

3

DNS 查询很昂贵,而且它们的结果很少变化,因此几乎每个网络实现都会缓存 DNS 查询结果,这样如果您在几秒钟内打开 100 个连接,它就不必执行 100 个返回相同 IP 地址的 DNS 查询。

Java 实现没有什么不同,它缓存了肯定和否定(失败的)查询。

默认情况下,否定结果会缓存 10 秒,但显然您的测试指出:

  1. 当执行另一次查找时,检查先前缓存的结果是否有效,从实现的角度来看这很好。
  2. 缓存不是构建考虑时钟变化的,所以每个人都应该考虑实时更改运行 java 网络东西的服务器的日期,以及许多其他不考虑时钟变化的软件包。

所以,发生的事情是 Java 缓存 test.com 是无法解析的。它应该只存在 10 秒,因此它带有时间戳。您更改了时钟,并且此时间戳不是在 10 秒后失效,而是可能在 14 天零 10 秒后失效。

您可以看到它正在使用缓存值,因为第一次单击按钮需要很长时间,而其他单击几乎是即时的。

一个简单的解决方案,也证明问题出在 DNS 缓存中,是将此行添加为 main 中的第一行:

java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "0");

这告诉 java 不要缓存负面的 DNS 结果,如果你尝试,你会看到它会按预期运行。但是,禁用此类缓存会大大降低您的性能,并导致安全问题,因此如果您突然更改机器的日期,我宁愿建议重新启动您的 JVM(以及大多数其他长时间运行的进程)。

于 2014-09-25T01:34:26.303 回答