0

I'm testing a class that handles communications based on a socket using jUnit 4. My test class launches a thread that simulates the client.

private class BeaconSimulator implements Runnable {

    private String address = null;
    private int port = 0;

    BeaconSimulator(String address, int port) {
        this.address = address;
        this.port = port;
    }

    @Override
    public void run() {
        try (
           Socket s = new Socket(address, port); 
           InputStream is = s.getInputStream();
 
           OutputStream os = s.getOutputStream()) {

           IOUtils.write(DatatypeConverter.parseHexBinary(
           "02000A00080001113E419F00D8000AB0ACB9AC309C22D84A11"), os);
           
           ack = IOUtils.toByteArray(is);

        } catch (UnknownHostException e) {
        
            System.err.print(e);
        
        } catch (IOException e) {
            
           System.err.print(e);
        }
    }

}

I launch it this way :

@Test
public void testBeaconCommunicationHandlerProcess() throws CustomException, InterruptedException, IOException {
    CustomBean bean = new CustomBean();
    ServerSocket server = new ServerSocket(8088);
    Thread t = new Thread(new BeaconSimulator("localhost", 8088));

    t.start();
    bean.setSocket(server.accept());
    new BeaconCommunicationHandler(bean).execute();
    t.join();
    assertArrayEquals(DatatypeConverter.parseHexBinary("0500000000"), ack);
    server.close();
}

The execute method of the BeaconCommunicationHandler object does the following :

LOG.info("Communication with {} started", getRunnableBean().getSocket().getInetAddress());
try (
   InputStream is = getRunnableBean().getSocket().getInputStream();
   OutputStream os = getRunnableBean().getSocket().getOutputStream()) {
    LOG.info("Reading MO on socket {}", getRunnableBean().getSocket().getInetAddress());
    try {
        message = IOUtils.toByteArray(is);
    } catch (IOException e) {
        throw new FunctionalGenException("Failed to read on socket", e);
    }
}
LOG.debug("MO from {} -> {}", getRunnableBean().getSocket().getInetAddress(), Hex.encodeHexString(message).toUpperCase());

LOG.info("Ending communication with {}", getRunnableBean().getSocket().getInetAddress());
try (DataOutputStream dos = new DataOutputStream(os)) {
    dos.write(DatatypeConverter.parseHexBinary("0500000000"));
} catch (IOException e) {
    throw new FunctionalGenException("Failed to send the final packet", e);
}

The problem is that when I don't try to read in my BeaconSimulator thread (by removing the line ack = IOUtils.toByteArray(is)), everything runs to the end, but if I try to read, the test blocks.

Without the line ack = IOUtils.toByteArray(is) :

02-07-2020 14:23:57 INFO     - main - BeaconCommunicationHandler     - Communication with /127.0.0.1 started
02-07-2020 14:23:57 INFO     - main - BeaconCommunicationHandler     - Reading MO on socket /127.0.0.1
02-07-2020 14:23:57 DEBUG    - main - BeaconCommunicationHandler     - MO from /127.0.0.1 -> 02000A00080001113E419F00D8000AB0ACB9AC309C22D84A11
02-07-2020 14:23:57 INFO     - main - BeaconCommunicationHandler     - Ending communication with /127.0.0.1
02-07-2020 14:23:57 INFO     - main - BeaconCommunicationHandler     - Communication with /127.0.0.1 ended

With the line ack = IOUtils.toByteArray(is) :

02-07-2020 13:51:07 INFO     - main - BeaconCommunicationHandler     - Communication with /127.0.0.1 started
02-07-2020 13:51:07 INFO     - main - BeaconCommunicationHandler     - Reading MO on socket /127.0.0.1

And it's stuck there.

Thank you for your help

Damian229
  • 482
  • 5
  • 10
Xobtah
  • 464
  • 7
  • 20

2 Answers2

1

Reading data from a socket

The problem could be the way you're creating the inputStream, do the following:

final ServerSocket server = new ServerSocket(port);
final Socket socket = server.accept();
final DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));

Wrapping the InputStream in a a DataInputStreamm allow you to read lines of a text, java primitives etc in a portable way, using specialized methods like readChar(), readInt() or readLine().

If the issue is the Position of the InputStream

When reset is supported by InputStream

Seems like the InputStream is used on the line

ack = IOUtils.toByteArray(is)

has been previusly readed, letting the Stream at the end of its position, so there is no way for getting the byte array from it. Try doing the following, let see if it solve your issue, and moving its position to the start of the stream:

   is.reset();
   ack = IOUtils.toByteArray(is);

I found this question really helpful for getting an answer.


When reset is not supported by InputStream

In case the reset is not supported and we want to tread the same information more than once, we can copy this data from your InputStream to a ByteArrayOutputStream.

Since Java 9 we can do this really quickly

final ByteArrayOutputStream os = new ByteArrayOutputStream();
final is.transferTo(os);
final InputStream clonedIs = new ByteArrayInputStream(os.ToByteArray());
Damian229
  • 482
  • 5
  • 10
  • Hello, thank you for your message, I get an error message : "java.io.IOException: mark/reset not supported" – Xobtah Jul 06 '20 at 07:50
  • I also tried to make a BufferedInputStream of my InputStream as described in the post, I get this error : "java.io.IOException: Resetting to invalid mark" – Xobtah Jul 06 '20 at 08:12
  • I will answer with another solution – Damian229 Jul 06 '20 at 08:54
  • @Xobtah I've updated the answer, does it solve you issue? – Damian229 Jul 06 '20 at 09:15
  • Unfortunately I need to work with Java 8 so the transferTo method is not on InputStream, but on the other hand IOUtils.copy seems to do just that, can you confirm that I can use "IOUtils.copy(is, os)" the same way as you use "is.transferTo(os)" ? Then in order to read on the stream, do I have to use "ack = IOUtils.toByteArray(clonedIs)" ? If the answer to these two questions is yes, then it is still stuck – Xobtah Jul 06 '20 at 09:28
  • Could you copy/clone/create a new InputStream each time you want to read it? Because once the InputStream has been read will not be available for being read again in this case – Damian229 Jul 06 '20 at 09:46
  • In this case I only read once, it is in the nested Runnable class BeaconSimulator, there is only one "ack = IOUtils.toByteArray(is)", but since I attempt to send data in the OutputStream I'm not sure of the interactions the two streams have with each other – Xobtah Jul 06 '20 at 09:57
  • I'm reviewing the code again, and I notice a possible issue on socket creation. I've updated the answer, does it help you? – Damian229 Jul 06 '20 at 10:10
  • I have updated the code both on client and server side, replacing each InputStream and OutputStream by their Data equivalent, using their read and write methods, but it is still stuck – Xobtah Jul 06 '20 at 11:22
  • It seems I have found something by removing IOUtils for reading, I'll do more tests and post what I will find – Xobtah Jul 06 '20 at 11:51
  • ok, thanks, sorry for not being able to get the issue! – Damian229 Jul 06 '20 at 12:45
  • another thing I notice is we need to close the OutputStream you're using ```IOUtils.write(DatatypeConverter.parseHexBinary( "02000A00080001113E419F00D8000AB0ACB9AC309C22D84A11"), os);``` Check this [question](https://stackoverflow.com/questions/27938125/ioutils-write-creates-new-file-but-doesnt-write-data) – Damian229 Jul 06 '20 at 12:48
  • The problem with this solution is that I need to read multiple times on the stream before closing it, there is a protocol between the server and the beacon and the connection closes after a certain message is sent, if I close the stream it also closes the socket's connection – Xobtah Jul 06 '20 at 12:56
1

Thanks to Damian229 I did some tinkering and found a solution. It seems the IOUtils methods are not working properly. I replaced the writing methods by the "native" OutputStream.write(byte[]) method that is simple to use, and replaced the read method by the following :

public byte[] readInputStream(InputStream is) throws IOException {
    byte[] message = null;
    byte[] buf = new byte[READ_SIZE];
    int readSize = -1;

    do {
        readSize = is.read(buf);
        if (readSize != -1) {
            buf = Arrays.copyOfRange(buf, 0, readSize);
        }
        message = message == null ? buf.clone() : ArrayUtils.addAll(message, buf);
    } while (readSize == READ_SIZE);
    return message;
}

I have made this method so it is used just as the IOUtils.read(InputStream) one, and now the communication is not stuck anymore. Thank you Damian229 for your time !

Xobtah
  • 464
  • 7
  • 20