【问题标题】:Java InputStream LockingJava 输入流锁定
【发布时间】:2012-01-26 05:41:57
【问题描述】:

我正在使用 InputStream 通过网络传输文件。

但是,如果我的网络在读取文件的过程中出现故障,则 read 方法会阻塞,并且如果网络重新出现,则永远不会恢复。

我想知道我应该如何处理这种情况,如果 InputStream 消失了,是否应该不抛出一些异常。

代码是这样的。

Url someUrl = new Url("http://somefile.com");
InputStream inputStream = someUrl.openStream();
byte[] byteArray = new byte[];
int size = 1024;
inputStream.read(byteArray,0,size);

所以在调用 read 之后某处网络出现故障并且 read 方法阻塞。

我该如何处理这种情况,因为读取似乎没有引发异常。

【问题讨论】:

标签: java inputstream


【解决方案1】:

通过查看此处的文档: http://docs.oracle.com/javase/6/docs/api/java/io/InputStream.html

看起来 read 确实引发了异常。

有几个选项可以解决您的具体问题。

一种选择是跟踪下载进度,并将该状态保留在程序的其他位置。然后,如果下载失败,您可以重新启动它并在失败点继续。

但是,如果下载失败,我会重新开始下载。无论如何,您都需要重新启动它,因此如果出现故障,您不妨从头开始重做整个事情。

【讨论】:

    【解决方案2】:

    简短的回答是使用 nio 包中的选择器。它们允许非阻塞网络操作。

    如果你打算使用旧的套接字,你可以尝试来自here的一些代码示例

    【讨论】:

    • 这真的是一个非常长的答案,因为它要编写的代码要多得多。处理停止响应的服务器的唯一方法是等待 X 个单位的时间并放弃。 TCP 已经在 J​​ava 的阻塞 IO 库中为我们提供了一些东西。
    【解决方案3】:

    运行一个单独的线程,该线程引用您的 InputStream,并在收到最后一个数据后重置其计时器 - 或类似的东西。如果 N 秒后该标志尚未重置,则让线程关闭 InputStream。 read(...) 将抛出一个 IOException,然后您可以从中恢复。

    您需要的类似于看门狗。像这样的:

    public class WatchDogThread extends Thread
    {
        private final Runnable timeoutAction;
        private final AtomicLong lastPoke = new AtomicLong( System.currentTimeMillis() );
        private final long maxWaitTime;
    
        public WatchDogThread( Runnable timeoutAction, long maxWaitTime )
        {
            this.timeoutAction = timeoutAction;
            this.maxWaitTime = maxWaitTime;
        }
    
        public void poke()
        {
            lastPoke.set( System.currentTimeMillis() );
        }
    
        public void run()
        {
            while( Thread.interrupted() ) {
                if( lastPoke.get() + maxWaitTime < System.currentTimeMillis() ) {
                    timeoutAction.run();
                    break;
                }
                try {
                    Thread.sleep( 1000 );
                } catch( InterruptedException e ) {
                    break;
                }
            }
        }
    }
    
    public class Example
    {
        public void method() throws IOException
        {
            final InputStream is = null;
            WatchDogThread watchDog =
                new WatchDogThread(
                    new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            try {
                                is.close();
                            } catch( IOException e ) {
                                System.err.println( "Failed to close: " + e.getMessage() );
                            }
                        }
                    },
                    10000
                );
            watchDog.start();
            try {
                is.read();
                watchDog.poke();
            } finally {
                watchDog.interrupt();
            }
        }
    }
    

    编辑:

    如前所述,套接字已经有一个timeout。这比做一个看门狗线程更可取。

    【讨论】:

    • 这正是您已经拥有的 Socket 超时。如果数据发送速度不够快并且达到超时,read() 将抛出 IOException,您可以安全地通知客户端它今天不会发生。如果没有创建更多线程的额外开销,这一切都将只是等待超时。另外,这是一个破坏线程池整个点的后门,并且可能由于外部服务器停止响应而导致创建 100 个线程。
    • 好的,这很公平。我将编辑我的回复。在发布回复之前我没有进行研究。
    【解决方案4】:

    这没什么大不了的。您需要做的就是设置连接超时。

    URL url = ...; 
    URLConnection conn = URL.openConnection(); 
    conn.setConnectTimeout( 30000 );
    conn.setReadTimeout(15000); 
    InputStream is = conn.openStream();
    

    最终,会发生以下情况之一。您的网络将恢复,您的传输将恢复,TCP 堆栈最终将超时,在这种情况下会引发异常,或者套接字将收到套接字关闭/重置异常并且您将收到 IOException。在所有情况下,线程都会放弃 read() 调用,您的线程将返回池中,准备好为其他请求提供服务,而无需您执行任何额外操作。

    例如,如果您的网络中断,您将不会获得任何新的连接,因此该线程已被绑定的事实不会产生任何影响,因为您没有连接进入。所以你的网络出去不是问题。

    更有可能的情况是您正在与之交谈的服务器可能会堵塞并停止向您发送数据,这也会减慢您的客户端的速度。这是调整超时比编写更多代码、使用 NIO 或单独线程等重要的地方。单独的线程只会增加机器的负载,并最终迫使您在超时后放弃线程,这正是 TCP 已经做到的给你。您也可能会破坏您的服务器,因为您正在为每个请求创建一个新线程,并且如果您开始放弃线程,您可能很容易结束 100 的线程都坐在那里等待那里的套接字超时。

    如果您的服务器上有大量流量通过此方法,并且任何依赖项(如外部服务器)造成的响应时间延迟都会影响您的响应时间。因此,您必须弄清楚您愿意等待多长时间,然后才出现错误并告诉客户端重试,因为您从中读取此文件的服务器没有足够快地放弃它。

    其他想法是在本地缓存文件,尝试限制您的网络旅行等,以限制您接触无响应的对等方。外部服务器上的数据库也可能发生完全相同的事情。如果您的数据库没有足够快地向您发送响应,它可能会堵塞您的线程池,就像一个没有足够快的文件一样。那么为什么对文件服务器有不同的担心呢?更多的错误处理不会解决你的问题,只会让你的代码变得迟钝。

    【讨论】:

    • 默认情况下,TCP 中没有读取超时。您必须明确设置一个。如果您正在发送,您只能依靠 TCP 计时器到期。
    • Ok 添加了一些说明以反映您需要设置超时。我认为 URL 会将它们设置为默认值,但我想不会。
    【解决方案5】:

    函数 inputStream.read() 是阻塞函数,应该在线程中调用。 有避免这种情况的替代方法。 InputStream 也有一个方法available()。它返回可以从流中读取的字节数。

    仅当流中有一些可用字节时才调用 read 方法。

    int length = 0;
    int ret = in.available();
    if(ret != 0){           
       length = in.read(recv);
    }
    

    InputStream 确实会抛出 IOException。希望这些信息对您有用。

    【讨论】:

    • 请注意,依赖available() 可能是个问题。即使套接字已关闭,它仍会返回0。判断连接是否从另一端终止的唯一方法是尝试从中读取;这是阻塞。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-09
    • 2012-07-19
    • 2012-11-17
    • 2018-05-25
    相关资源
    最近更新 更多