development

시간 초과로 InputStream에서 읽을 수 있습니까?

big-blog 2020. 6. 21. 19:07
반응형

시간 초과로 InputStream에서 읽을 수 있습니까?


특히, 문제는 다음과 같은 방법을 작성하는 것입니다.

int maybeRead(InputStream in, long timeout)

여기서 'timeout'밀리 초 내에 데이터를 사용할 수 있으면 반환 값은 in.read ()와 동일하고 그렇지 않으면 -2입니다. 메소드가 리턴되기 전에, 생성 된 모든 스레드가 종료되어야합니다.

인수를 피하기 위해 Sun (모든 Java 버전)이 문서화 한 주제는 java.io.InputStream입니다. 이것은 보이는 것처럼 간단하지 않습니다. 다음은 Sun의 설명서에서 직접 지원되는 몇 가지 사실입니다.

  1. in.read () 메소드는 인터럽트 할 수 없습니다.

  2. Reader 또는 InterruptibleChannel에서 InputStream을 래핑하는 것은 도움이되지 않습니다. 모든 클래스가 할 수있는 것은 InputStream의 메소드를 호출하는 것입니다. 해당 클래스를 사용할 수 있다면 InputStream에서 동일한 논리를 직접 실행하는 솔루션을 작성할 수 있습니다.

  3. in.available ()이 항상 0을 반환하는 것은 허용됩니다.

  4. in.close () 메소드는 차단하거나 아무 것도 할 수 없습니다.

  5. 다른 스레드를 죽이는 일반적인 방법은 없습니다.


inputStream.available () 사용

System.in.available ()이 항상 0을 반환하는 것은 허용됩니다.

나는 그 반대를 찾았습니다-항상 사용 가능한 바이트 수에 가장 적합한 값을 반환합니다. 에 대한 Javadoc InputStream.available():

Returns an estimate of the number of bytes that can be read (or skipped over) 
from this input stream without blocking by the next invocation of a method for 
this input stream.

타이밍 / 정말로 인해 추정치를 피할 수 없습니다. 새로운 데이터가 지속적으로 도착하기 때문에 수치는 과소 평가 될 수 있습니다. 그러나 항상 다음 통화에서 "캐치"합니다. 도착한 모든 데이터, 즉 새 통화 순간 도착한 데이터를 고려해야합니다. 위의 조건에 데이터가 실패하면 영구적으로 0을 반환합니다.

첫 번째주의 사항 : InputStream의 구체적인 하위 클래스는 available ()을 담당합니다.

InputStream추상 클래스입니다. 데이터 소스가 없습니다. 사용 가능한 데이터를 갖는 것은 의미가 없습니다. 따라서 javadoc available()도 상태를 나타냅니다.

The available method for class InputStream always returns 0.

This method should be overridden by subclasses.

실제로 구체적인 입력 스트림 클래스는 available ()을 재정 의하여 상수 0이 아닌 의미있는 값을 제공합니다.

두 번째주의 사항 : Windows에서 입력을 입력 할 때 캐리지 리턴을 사용하십시오.

를 사용하는 경우 System.in명령 쉘이 넘겨 줄 때만 프로그램이 입력을받습니다. 파일 리디렉션 / 파이프를 사용하는 경우 (예 : somefile> java myJavaApp 또는 somecommand | java myJavaApp) 입력 데이터는 일반적으로 즉시 전달됩니다. 그러나 입력을 수동으로 입력하면 데이터 핸드 오버가 지연 될 수 있습니다. 예를 들어 Windows cmd.exe 셸을 사용하면 cmd.exe 셸 내에 데이터가 버퍼링됩니다. 캐리지 리턴 (control-m 또는 <enter>) 다음에 실행중인 Java 프로그램으로 만 데이터가 전달됩니다 . 그것은 실행 환경의 한계입니다. 물론, InputStream.available ()은 쉘이 데이터를 버퍼링하는 한 0을 반환합니다. 올바른 동작입니다. 해당 시점에 사용 가능한 데이터가 없습니다. 셸에서 데이터를 사용할 수있게되면이 메서드는 0보다 큰 값을 반환합니다. NB : Cygwin은 cmd.exe도 사용합니다.

가장 간단한 솔루션 (차단 없음, 시간 초과 필요 없음)

이것을 사용하십시오 :

    byte[] inputData = new byte[1024];
    int result = is.read(inputData, 0, is.available());  
    // result will indicate number of bytes read; -1 for EOF with no data read.

또는 동등하게

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in, Charset.forName("ISO-8859-1")),1024);
    // ...
         // inside some iteration / processing logic:
         if (br.ready()) {
             int readCount = br.read(inputData, bufferOffset, inputData.length-bufferOffset);
         }

보다 풍부한 솔루션 (시간 초과 기간 내에 버퍼를 최대로 채움)

이것을 선언하십시오 :

public static int readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis)
     throws IOException  {
     int bufferOffset = 0;
     long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
     while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {
         int readLength = java.lang.Math.min(is.available(),b.length-bufferOffset);
         // can alternatively use bufferedReader, guarded by isReady():
         int readResult = is.read(b, bufferOffset, readLength);
         if (readResult == -1) break;
         bufferOffset += readResult;
     }
     return bufferOffset;
 }

그런 다음 이것을 사용하십시오 :

    byte[] inputData = new byte[1024];
    int readCount = readInputStreamWithTimeout(System.in, inputData, 6000);  // 6 second timeout
    // readCount will indicate number of bytes read; -1 for EOF with no data read.

스트림이 소켓에 의해 지원되지 않기 때문에 (사용할 수 없음 Socket.setSoTimeout())이 유형의 문제를 해결하는 표준 방법은 미래를 사용하는 것입니다.

다음 실행기와 스트림이 있다고 가정하십시오.

    ExecutorService executor = Executors.newFixedThreadPool(2);
    final PipedOutputStream outputStream = new PipedOutputStream();
    final PipedInputStream inputStream = new PipedInputStream(outputStream);

필자는 일부 데이터를 쓴 다음 마지막 데이터를 쓰고 스트림을 닫기 전에 5 초 동안 기다리는 작성자가 있습니다.

    Runnable writeTask = new Runnable() {
        @Override
        public void run() {
            try {
                outputStream.write(1);
                outputStream.write(2);
                Thread.sleep(5000);
                outputStream.write(3);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    executor.submit(writeTask);

이것을 읽는 일반적인 방법은 다음과 같습니다. 읽기는 데이터에 대해 무기한 차단되므로 5 초 안에 완료됩니다.

    long start = currentTimeMillis();
    int readByte = 1;
    // Read data without timeout
    while (readByte >= 0) {
        readByte = inputStream.read();
        if (readByte >= 0)
            System.out.println("Read: " + readByte);
    }
    System.out.println("Complete in " + (currentTimeMillis() - start) + "ms");

어떤 출력 :

Read: 1
Read: 2
Read: 3
Complete in 5001ms

작가가 응답하지 않는 것처럼보다 근본적인 문제가 있었다면 독자는 영원히 차단했을 것입니다. 나중에 읽기를 래핑하면 다음과 같이 시간 초과를 제어 할 수 있습니다.

    int readByte = 1;
    // Read data with timeout
    Callable<Integer> readTask = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return inputStream.read();
        }
    };
    while (readByte >= 0) {
        Future<Integer> future = executor.submit(readTask);
        readByte = future.get(1000, TimeUnit.MILLISECONDS);
        if (readByte >= 0)
            System.out.println("Read: " + readByte);
    }

어떤 출력 :

Read: 1
Read: 2
Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
    at java.util.concurrent.FutureTask.get(FutureTask.java:91)
    at test.InputStreamWithTimeoutTest.main(InputStreamWithTimeoutTest.java:74)

I can catch the TimeoutException and do whatever cleanup I want.


I would question the problem statement rather than just accept it blindly. You only need timeouts from the console or over the network. If the latter you have Socket.setSoTimeout() and HttpURLConnection.setReadTimeout() which both do exactly what is required, as long as you set them up correctly when you construct/acquire them. Leaving it to an arbitrary point later in the application when all you have is the InputStream is poor design leading to a very awkward implementation.


If your InputStream is backed by a Socket, you can set a Socket timeout (in milliseconds) using setSoTimeout. If the read() call doesn't unblock within the timeout specified, it will throw a SocketTimeoutException.

Just make sure that you call setSoTimeout on the Socket before making the read() call.


I have not used the classes from the Java NIO package, but it seems they might be of some help here. Specifically, java.nio.channels.Channels and java.nio.channels.InterruptibleChannel.


Here is a way to get a NIO FileChannel from System.in and check for availability of data using a timeout, which is a special case of the problem described in the question. Run it at the console, don't type any input, and wait for the results. It was tested successfully under Java 6 on Windows and Linux.

import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;

public class Main {

    static final ByteBuffer buf = ByteBuffer.allocate(4096);

    public static void main(String[] args) {

        long timeout = 1000 * 5;

        try {
            InputStream in = extract(System.in);
            if (! (in instanceof FileInputStream))
                throw new RuntimeException(
                        "Could not extract a FileInputStream from STDIN.");

            try {
                int ret = maybeAvailable((FileInputStream)in, timeout);
                System.out.println(
                        Integer.toString(ret) + " bytes were read.");

            } finally {
                in.close();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /* unravels all layers of FilterInputStream wrappers to get to the
     * core InputStream
     */
    public static InputStream extract(InputStream in)
            throws NoSuchFieldException, IllegalAccessException {

        Field f = FilterInputStream.class.getDeclaredField("in");
        f.setAccessible(true);

        while( in instanceof FilterInputStream )
            in = (InputStream)f.get((FilterInputStream)in);

        return in;
    }

    /* Returns the number of bytes which could be read from the stream,
     * timing out after the specified number of milliseconds.
     * Returns 0 on timeout (because no bytes could be read)
     * and -1 for end of stream.
     */
    public static int maybeAvailable(final FileInputStream in, long timeout)
            throws IOException, InterruptedException {

        final int[] dataReady = {0};
        final IOException[] maybeException = {null};
        final Thread reader = new Thread() {
            public void run() {                
                try {
                    dataReady[0] = in.getChannel().read(buf);
                } catch (ClosedByInterruptException e) {
                    System.err.println("Reader interrupted.");
                } catch (IOException e) {
                    maybeException[0] = e;
                }
            }
        };

        Thread interruptor = new Thread() {
            public void run() {
                reader.interrupt();
            }
        };

        reader.start();
        for(;;) {

            reader.join(timeout);
            if (!reader.isAlive())
                break;

            interruptor.start();
            interruptor.join(1000);
            reader.join(1000);
            if (!reader.isAlive())
                break;

            System.err.println("We're hung");
            System.exit(1);
        }

        if ( maybeException[0] != null )
            throw maybeException[0];

        return dataReady[0];
    }
}

Interestingly, when running the program inside NetBeans 6.5 rather than at the console, the timeout doesn't work at all, and the call to System.exit() is actually necessary to kill the zombie threads. What happens is that the interruptor thread blocks (!) on the call to reader.interrupt(). Another test program (not shown here) additionally tries to close the channel, but that doesn't work either.


As jt said, NIO is the best (and correct) solution. If you really are stuck with an InputStream though, you could either

  1. Spawn a thread who's exclusive job is to read from the InputStream and put the result into a buffer which can be read from your original thread without blocking. This should work well if you only ever have one instance of the stream. Otherwise you may be able to kill the thread using the deprecated methods in the Thread class, though this may cause resource leaks.

  2. Rely on isAvailable to indicate data that can be read without blocking. However in some cases (such as with Sockets) it can take a potentially blocking read for isAvailable to report something other than 0.

참고URL : https://stackoverflow.com/questions/804951/is-it-possible-to-read-from-a-inputstream-with-a-timeout

반응형