3
\$\begingroup\$

I am interfacing with a network device and must read a response after I issue a command. Sometimes when I attempt to read, nothing has been returned from the terminal. This means I must wait, but not forever, right?

My solution: Enter a loop with a time comparison guard (time elapsed since start). Then inside, I will check to see if there is anything available to read. If so read it and break of the loop if nothing left.

I couldn't figure out another approach. Having a while loop on the number of bytes read was fine up to the point where there was no data left, in which case it would just wait until the timeout to occur. My way will return as soon as everything has bead read or else timeout occurs.

Does this make sense? I currently have the timeout set to 10 seconds.

private Socket socket;
private DataInputStream reader;
private DataOutputStream writer;            

socket = new Socket(device.getHostAddress(), device.getHostPort());

socket.setSoTimeout((int) READ_TIMEOUT_MILLIS);

reader = new DataInputStream(socket.getInputStream());
writer = new DataOutputStream(socket.getOutputStream());

writer.write(data.getBytes(ENCODING_CHARSET));
writer.flush();

ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();

byte[] buffer = new byte[1024];

long opStartMillis = System.currentTimeMillis();

while(System.currentTimeMillis() - opStartMillis < READ_TIMEOUT_MILLIS) {

    if(reader.available() > 0) {

        int bytesRead = reader.read(buffer);

        bufferStream.write(buffer, 0, bytesRead);

        if(reader.available() == 0) {

            break;
        }
    }
}

callback.dataReceived(bufferStream.toString(ENCODING_CHARSET));
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Too late for OP but anyway...

Seems that you read from socket continuously when data is available. If there is not incoming data in socket for some reason you can wait maximum 10 seconds (it's just an exemplary number) and then you terminate the program.

Some kind of loop is required anyway (because you read as much as possible). The loop can be hidden with aid of Java's Timer, but it will not make the code more clean.

Please consider the following example (I played with files instead of sockets, but from data stream's point of view they expose the same interface):

public class DataInputStreamWithTimeoutExample {
    public static final int TIMEOUT = 10;

    public static void main(String args[]) throws IOException, ExecutionException, InterruptedException {   
        final DataInputStream reader =
                new DataInputStream(new FileInputStream("/home/me/Temp/java_example/in.txt"));
        final DataOutputStream writer =
                new DataOutputStream(new FileOutputStream("/home/me/Temp/java_example/out.txt"));

        final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        final Future<Void> future = singleThreadExecutor.submit(new Courier(reader, writer));

        try {
            System.out.println("Started...");
            future.get(TIMEOUT, TimeUnit.SECONDS);
            System.out.println("Finished.");
        } catch (TimeoutException e) {
            System.out.println("Timeout has gone!");

            // TIMEOUT seconds are passed, but infinite loop is not finished.
            // Lets send `interrupt` message to it.
            future.cancel(true);
            singleThreadExecutor.shutdown();
        }
    }
}

class Courier implements Callable<Void> {
    public static final int BUFFER_SIZE = 1024;

    private final DataInputStream reader;
    private final DataOutputStream writer;

    public Courier(final DataInputStream reader, final DataOutputStream writer) {
        this.reader = reader;
        this.writer = writer;
    }

    @Override
    public Void call() throws Exception {
        while (true) {
            int availableBytes = reader.available();
            if (availableBytes > 0) {
                final byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead = reader.read(buffer);
                writer.write(buffer, 0, bytesRead);
            }
            // We need analyze whether someone decided 
            // to interrupt the infinite loop.
            if (Thread.interrupted()) {
                writer.flush();
                writer.close();
                System.out.println("Terminated.");
                return null;
            }
        }
    }
}

We analyze status of the thread inside infinite loop because without such check it will be impossible to interrupt the loop outside it.

You can ask Where is the gain?

  1. We wiped out System.currentTimeMillis()... stuff. Java's ExecutorService does calculations of time for us.

  2. Timeout is set outside the loop (please note that there are not any references to TIMEOUT constant inside Courier class). So Courier class can be re-used in another program (but don't forget that you need to send interrupt message to Courier with aid of future.cancel(true); and singleThreadExecutor.shutdown();, otherwise the program will never end).

PS while working on this answer I used awesome answers:

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.