Java输入流锁定

3

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

然而,如果我的网络中断,读取文件的过程将被阻塞,并且如果网络重新出现,则永远无法恢复读取方法。

我想知道如何处理这种情况,以及如果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方法被阻塞。

由于read方法似乎没有抛出异常,我该如何处理这种情况?


1
请问这个问题与编程有关吗?https://dev59.com/jnRA5IYBdhLWcg3w2xsI - Mikeb
可能是在Java中恢复HTTP文件下载的简历的重复问题。 - Oleg Mikheev
尝试使用openConnection,然后在打开流之前修改返回的URLConnection对象。 - home
5个回答

0

有一个单独的线程正在运行,它具有对您的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();
        }
    }
}

编辑:

如注意到的那样,套接字已经有超时了。这比使用看门狗线程更可取。


1
这就是使用套接字超时所已经拥有的功能。如果数据发送不够快,达到了超时时间,read() 将抛出一个 IOException,你可以安全地通知客户端它今天不会发生了。无需创建更多线程的额外开销,所有这些都只会坐等超时。此外,这是一个绕过线程池整体目的的后门,可能会因为外部服务器停止响应而导致创建数百个线程。 - chubbsondubs
好的,没问题。我会编辑我的回复。在发表回复之前我没有做足研究。 - Ravi Wallau

0

函数inputStream.read()是一个阻塞函数,应该在线程中调用。

有一种避免这种情况的替代方法。InputStream也有一个方法available()。它返回可以从流中读取的字节数。

仅在流中有可用字节时调用read方法。

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

InputStream会抛出IOException异常。希望这个信息对您有用。


请注意,依赖 available() 可能会出现问题。即使套接字已关闭,它仍将返回 0。唯一确定连接是否已从另一端终止的方法是尝试从中读取;这是阻塞的。 - Zhro

0

从这里的文档http://docs.oracle.com/javase/6/docs/api/java/io/InputStream.html来看:

read方法似乎会抛出异常。

有几个选项可以解决您的特定问题。

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

但是,如果下载失败,我会选择重新启动下载。无论如何,您都需要重新启动它,因此如果发生故障,最好从头开始重新做整件事情。


0

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

如果您打算使用旧套接字,可以尝试从这里获取一些代码示例。


1
这真的是一个非常长的答案,因为需要编写大量代码来完成此操作。而且你唯一能处理无响应服务器的方法就是等待X个时间单位并放弃。即使在Java的阻塞IO库中,TCP也已经为我们提供了这种功能。 - chubbsondubs

-1

这不是什么大问题。你只需要在连接上设置一个超时时间。

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

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

例如,如果您的网络断开,您将不会收到任何新连接进来,因此这个线程被绑定不会有任何影响,因为您没有连接进来。所以您的网络断开并不是问题所在。

更可能的情况是,您正在通信的服务器可能会被卡住并停止发送数据,这也会降低您的客户端速度。这就是调整超时时间比编写更多代码、使用NIO或单独线程等方案重要的原因。单独的线程只会增加机器的负荷,并最终迫使您在超时后放弃该线程,而这正是TCP已经提供给您的。此外,您还可能破坏您的服务器,因为您正在为每个请求创建一个新线程,如果您开始放弃线程,您很容易会得到100多个线程,全部围着等待套接字超时。

如果您的服务器上有大量流量通过此方法,而依赖项(如外部服务器)响应时间的任何延迟都会影响您的响应时间。因此,您必须确定在告诉客户端再次尝试之前,您愿意等待多长时间,因为您正在读取此文件的服务器没有快速释放它。

其他想法是本地缓存文件,尝试限制网络传输等,以限制您对不响应的对等方的暴露。外部服务器上的数据库也可能发生完全相同的情况。如果您的数据库没有及时发送响应,它就会像下载速度不够快的文件一样阻塞您的线程池。那么,为什么要对文件服务器担心得与众不同呢?更多的错误处理并不能解决您的问题,只会使您的代码变得晦涩难懂。


默认情况下,TCP 中没有读取超时。您必须显式设置一个超时时间。如果您正在发送数据,则只能依赖于 TCP 计时器的到期。 - user207421
好的,我添加了一些澄清来反映您需要设置超时时间。我以为URL会将它们设置为默认值,但我猜不是。 - chubbsondubs

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