[java] 시간 초과로 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을 재정 의하여 상수 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를 사용합니다.

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

이것을 사용하십시오 :

    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)

TimeoutException을 포착하고 원하는 정리 작업을 수행 할 수 있습니다.


답변

InputStream이 Socket에 의해 지원되는 경우 setSoTimeout을 사용하여 소켓 제한 시간 (밀리 초)을 설정할 수 있습니다 . read () 호출이 지정된 시간 초과 내에 차단 해제되지 않으면 SocketTimeoutException이 발생합니다.

read () 호출하기 전에 소켓에서 setSoTimeout을 호출해야합니다.


답변

나는 맹목적으로 받아들이 기보다는 문제 진술에 의문을 제기 할 것이다. 콘솔 또는 네트워크를 통한 시간 초과 만 필요합니다. 후자를 가지고 Socket.setSoTimeout()있고 HttpURLConnection.setReadTimeout()둘 다 정확히 필요한 것을 수행하는 경우, 구성 / 획득 할 때 올바르게 설정하기 만하면됩니다. 입력 스트림이 디자인이 좋지 않은 경우 응용 프로그램에서 나중에 임의의 지점으로두면 디자인이 매우 어색합니다.


답변

Java NIO 패키지의 클래스를 사용하지 않았지만 여기에 도움이 될 것 같습니다 . 특히 java.nio.channels.Channelsjava.nio.channels.InterruptibleChannel 입니다.


답변

다음은 System.in에서 NIO FileChannel을 가져오고 타임 아웃을 사용하여 데이터의 가용성을 확인하는 방법입니다. 이는 질문에 설명 된 문제의 특별한 경우입니다. 콘솔에서 실행하고 입력을 입력하지 말고 결과를 기다리십시오. Windows 및 Linux의 Java 6에서 성공적으로 테스트되었습니다.

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];
    }
}

흥미롭게도 콘솔이 아닌 NetBeans 6.5에서 프로그램을 실행할 때 시간 초과가 전혀 작동하지 않으며 실제로 좀비 스레드를 종료하려면 System.exit () 호출이 필요합니다. 리더 .interrupt () 호출시 인터럽트 스레드 스레드 (!)가 발생합니다. 다른 테스트 프로그램 (여기에 표시되지 않음)은 채널을 닫으려고 시도하지만 작동하지 않습니다.


답변

jt가 말했듯이 NIO는 최고의 (올바른) 솔루션입니다. 그래도 실제로 InputStream에 갇혀 있다면

  1. 독점 작업 인 스레드를 생성하면 InputStream에서 읽고 결과를 버퍼에 넣어 버퍼를 원래 스레드에서 읽을 수 있습니다. 스트림 인스턴스가 하나만있는 경우에는 잘 작동합니다. 그렇지 않으면 Thread 클래스에서 사용되지 않는 메서드를 사용하여 스레드를 종료 할 수 있지만 리소스 누수가 발생할 수 있습니다.

  2. isAvailable을 사용하여 차단하지 않고 읽을 수있는 데이터를 나타냅니다. 그러나 (소켓과 같은) 경우에 따라 isAvailable이 0 이외의 다른 것을보고하기 위해서는 잠재적으로 블로킹 읽기가 필요할 수 있습니다.