为什么连接终止时没有抛出异常?

4

我在这里解决一个问题。希望从专家那里得到一些帮助。

下面是Java中简单的文件下载器代码。我想在下载文件时检测连接断开的异常。现在,我运行代码,在下载文件的过程中,使用键盘wifi关闭按钮关闭我的wifi。这样做,程序就会永远挂起而不抛出任何异常或终止。它似乎永远阻塞。首先让我困惑的是为什么没有抛出异常?其次,在以下代码中,您可以看到这行//con.setReadTimeout(2000);,目前已被注释掉。去掉注释并运行程序,现在如果通过关闭wifi中断连接,那么它等待2秒钟,然后如果不能读取则终止而不抛出任何异常。所以,同样的情况下,为什么它只终止而不抛出任何异常呢?我对这种行为非常困惑。希望能得到一些帮助。非常感谢!

import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.*;

    public class Main {
        public static void main(String[] args) {

            try{

                URL website = new URL("http://128f1.downloadming1.com/bollywood%20mp3/Jai%20Ho%20(2014)/06%20-%20Naacho%20Re%20-%20DownloadMing.SE.mp3");

                URLConnection con = website.openConnection();


                //con.setReadTimeout(2000);



                ReadableByteChannel rbc = Channels.newChannel(con.getInputStream());
                FileOutputStream fos = new FileOutputStream("song.mp3");

                fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);


            }
            catch(Exception e){

                System.out.println("got here");
                e.printStackTrace();
            }



            System.out.println("Done downloading...");

        }
    }

1
对于超时情况,FileChannel 实现(来自 fos.getChannel())可能会吞掉异常并返回已写入的字节数(如果有)。至于其他情况,我不确定。尝试检查流的类型并查看它们的实现。 - Sotirios Delimanolis
这个问题的前半部分已经被同一用户在一天之前提出过了:http://stackoverflow.com/questions/24975455/how-do-you-detect-a-network-disconnection-when-downloading-a-file-in-java - Fabian Ritzmann
1个回答

1
TCP连接是为处理不可靠的网络和数据包丢失而设计的。如果您的网络连接中断,它将不会知道,除非您设置SO_KEEPALIVE套接字选项(java.net.SocketOptions有关于该选项的一些文档)。
不幸的是,URLConnection不允许您设置此选项或任何其他套接字选项。即使它允许,保持活动间隔也会很高,在大多数操作系统上为两个小时。保持活动间隔只能在操作系统设置中更改,不能通过Java更改(在这方面,SocketOptions.SO_KEEPALIVE的文档令人困惑)。
总之,您没有收到异常,因为底层网络协议不设计检测连接初始建立后是否中断了网络介质。
正如您发现的那样,setReadTimeout有所帮助,但是您已经遇到了transferFrom方法的一个未记录的特性。该方法的实际实现在sun.nio.ch.FileChannelImpl.transferFromArbitraryChannel中。我在此粘贴JDK 7代码:
private long transferFromArbitraryChannel(ReadableByteChannel src,
                                          long position, long count)
    throws IOException
{
    // Untrusted target: Use a newly-erased buffer
    int c = (int)Math.min(count, TRANSFER_SIZE);
    ByteBuffer bb = Util.getTemporaryDirectBuffer(c);
    long tw = 0;                    // Total bytes written
    long pos = position;
    try {
        Util.erase(bb);
        while (tw < count) {
            bb.limit((int)Math.min((count - tw), (long)TRANSFER_SIZE));
            // ## Bug: Will block reading src if this channel
            // ##      is asynchronously closed
            int nr = src.read(bb);
            if (nr <= 0)
                break;
            bb.flip();
            int nw = write(bb, pos);
            tw += nw;
            if (nw != nr)
                break;
            pos += nw;
            bb.clear();
        }
        return tw;
    } catch (IOException x) {
        if (tw > 0)
            return tw;
        throw x;
    } finally {
        Util.releaseTemporaryDirectBuffer(bb);
    }
}

正如您在catch块中所看到的,如果一些数据已经被写入目标位置,它将忽略任何IOException并返回已写入的字节数。

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