Fault-tolerant UDP clients in non-blocking mode

One by one sending and acknowledging of identified packets

The aim of this lab session is to re-implement the two clients of the lab session on UDP reliabiliy, but now in non-blocking mode. You could refer to Fault-tolerant UDP clients for a detailed presentation of the IdUpperCase protocol and the behavior of the clients ClientIdUpperCaseUDPOneByOne and ClientIdUpperCaseUDPBurst.
Protocol IdUpperCase
  • a long id in BigEndian identifying the request/response,
  • the bytes of the string encoded in UTF-8.
The answer of the server has the same format using the id of the request. The packets exchanged in this protocol have a maximal size of 1024 bytes.

The program ClientIdUpperCaseUDPOneByOne accepts as parameters the input file name (in-filename), the output file name ( out-filename), the timeout before resending in milliseconds (timeout) and the IdUpperCase server address (hostname port). For instance,

$ java fr.uge.net.udp.nonblocking.ClientIdUpperCaseUDPOneByOne in.txt out.txt 300 localhost 7777
The client ClientIdUpperCaseUDPOneByOne sends one by one the lines found in file in-filename to the server, and writes the responses in the file out-filename, both exchanged with respect to the protocol IdUpperCase.
For instance, if there are two lines in in-filename, the client sends a request for the first line with id 0. If no response with id 0 is received in timeout milliseconds, the client re-sends the same request, and again until a response with id 0 is received. Then, the client sends the request for the second line with id 1, and so on.

The major difference is that both sending and receiving operations will be performed in the same thread, using non-blocking IO.

Starting from the code ClientIdUpperCaseUDPOneByOne.java, implement the non-blocking client ClientIdUpperCaseUDPOneByOne.

Method selector.select() is blocking: so, a call to selector.select() returns only when (at least one) a monitored opertation is detected. For our client, we need to limit the waiting time in selector.select() to be able to re-send a request when no response is received after timeout milliseconds.
It is possible to call selector.select(int delay), which returns after at most delay milliseconds even if nothing is selected.
In our code template, we use a State state to store our current state in the protocol.
enum State {SENDING, RECEIVING, FINISHED};
Our client is in SENDING state if he wants to send a request; if he is in RECEIVING state then he is waiting for answers and he is in state FINISHED when he received the response of the last line request.
Thus, we have slightly modified the selection loop:
while (!isFinished()) {
  try {
    selector.select(this::treatKey,updateInterestOps());
  } catch(UncheckedIOException tunneled) {
    throw tunneled.getCause();
  }
}
dc.close();
Since our selector only monitors a single DatagramChannel, there is only one key to consider, which we store in the field uniqueKey of the client class. We assume that only the updateInterestOps() method is allowed to change the interestOps value of this uniqueKey. Furthermore, this updateInterestOps() method returns the maximum amount of time we can wait in selector.select().
Be careful: selector.select(0) is equivalent to selector.select(), which may wait indefinitely.

To test your client

We provide you with a server ServerIdUpperCase.jar and you could use the UDP proxy UDPProxy.jar that simulates losses and delays in exchanges.

$ java -jar ServerIdUpperCaseUDP.jar 4545 UTF-8
$ java -jar UDPProxy.jar 7777 localhost 4545 -no-swap 

Test your code with file in.txt as follows:

$ java fr.uge.net.udp.nonblocking.ClientIdUpperCaseUDPOneByOne in.txt out.txt 300 localhost 7777

Do not forget to use the proxy...

Check that your client does not re-send its request too early, that is before waiting timeout milliseconds since its last attempt... This could be the case when receiving a response to another request (with an unexpected ID) if your client immediately re-sends the request without checking the time elapsed sine last sending.

To identify such a situation, we provide you with a fake server ServerIdUpperCaseTestTimeout.jar. Instead of normaly answering to a received request (by sending the uppercase string), this fake server arbitrarily answers to the client, every 100 milliseconds, a fake response with an id -1 ; it also prints the time elpased between all received packets.

You shoud test with:

$ java -jar ServerIdUpperCaseUDPTestTimeout.jar 4545
$ java fr.uge.net.udp.nonblocking.ClientIdUpperCaseUDPOneByOne in.txt out.txt 300 localhost 4545
And if your client is correct the server should print times close to the timeout.
    Time since last receive: 301 ms
    Time since last receive: 301 ms
    Time since last receive: 301 ms
    Time since last receive: 301 ms
    Time since last receive: 300 ms
    Time since last receive: 301 ms
    ....

Do not use the proxy for this last test.

Burst sending of identified packets, asynchronously acknowledged

Starting from the same draft of code ClientIdUpperCaseUDPOneByOne.java, you now have to implement a client ClientIdUpperCaseUDPBurst that sends burst of requests.

You shoud test with file in.txt as follows:

$ java -jar ServerIdUpperCaseUDP.jar 4545 UTF-8
$ java -jar UDPProxy.jar 7777 localhost 4545 -no-swap 
$ java fr.uge.net.udp.nonblocking.ClientIdUpperCaseUDPBurst in.txt out.txt 300 localhost 7777
    

You are only allowed to perform one send call in the doWrite method. For each send operation, you have to ask for the permission of the selector.