In this exercise, we want to write a non-blocking TCP server which sums integers. The server will have only one thread but will handle multiple clients concurrently.
int
(4 byte) in Big Endian and
then closes the connexion for writing. The server responds with a single int
in Big Endian, which is the sum of the two integers sent by the client,
and then closes the connexion.
Starting from ServerSumOneShot.java, write a non-blocking server ServerSumOneShot
for the protocole SumOneShot.
It is not necessary to check if the client has closed the connexion after sending the two int
: after sending the response, the server simply closes the connexion.
attachement
field
of the SelectionKey a
ByteBuffer
with capacity 2*Integer.BYTES
(this buffer is created and attached when the client is accepted). soceketChannel.write
(as well as the socketChannel.read
) is non-blocking and
that there is no guaranteee that the entire work-zone of the ByteBuffer will be written in just one call. Check your code to see what would happen if the call soceketChannel.write
to give the answer to the client writes only 2 bytes out of the 4 bytes. This is unlikely but it could happen and your code should handle it.
processSelectedKeys
, how should you handle the exceptions by the methods doRead
, doWrite
and doAccept
?
To test your code, use the jar ClientSumOneShot.jar which reads two integers from the keyboard, sends them to the server and prints the answer from the server.
% java fr.upem.net.tcp.nonblocking.ServerSumOneShot 7777 % java -jar ClientSumOneShot.jar localhost 7777 Connected to server. Enter two integers: 1 2 Waiting for server's reply ... Server replied : 3
By using the -bug option of ClientSumOneShot.jar
, you can simulate a client which closes its connexion before sending
the two ints. In this case your server should close the connexion because
the client does not respect the protocol, but should continue to accept connections.
% java fr.upem.net.tcp.nonblocking.ServerSumOneShot 7777 % java -jar ClientSumOneShot.jar localhost 7777 -bug Connected to server. Enter two integers: 1 2 Closing the write-connection without sending the second INT. Waiting for server's reply ... Server closed the connection before sending the answer (This is the normal behavior).
We now change the protocol to allow a client to send multiple sums over the same connection.
int
(4 bytes each) in Big Endian. The server responds with an int
(4 bytes) in Big Endian equal to the sum of the two ints sent by the client. These exchanges go on until the client closes the connexion for writing.
Copy you server from the previous question and adapt the code to
produce a server ServerSum
for this new protocol.
% java fr.upem.net.tcp.nonblocking.ServerSum 7777 % java -jar ClientTestServerMulti.jar localhost 7777
Client 0 connected to server. Client 1 connected to server. Client 2 connected to server. Client 3 connected to server. Client 4 connected to server. Client 1 finished sending its requests. Client 2 finished sending its requests. Client 3 finished sending its requests. Client 0 finished sending its requests. Client 4 finished sending its requests. Client 1 finished receiving the answers. Client 0 finished receiving the answers. Client 4 finished receiving the answers. Client 3 finished receiving the answers. Client 2 finished receiving the answers.
The goal of this execrise is to write a server for the protocol Echo.
We will take the occasion to better structure our non-blocking servers using what we call a Context
.
We would like our server to be able to be have simultaneously OP_READ
as well as OP_WRITE
as
operations of interest. In order to do this, we will attach an object of class Context
to each SelectionKey
correspoding to a SocketChannel
of a client.
static private class Context { final private SelectionKey key; final private SocketChannel sc; final private ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); private boolean closed = false; private Context(SelectionKey key){ this.key = key; this.sc = (SocketChannel) key.channel(); } private void updateInterestOps() { // TODO } private void doRead() throws IOException { // TODO } private void doWrite() throws IOException { // TODO } }
The principle of the class Context
is that it contains all of the information regarding
the associated SocketChannel
, i.e.,
associated with the corresponding client.
The class will always contain the SocketChannel
and the SelectionKey
associated.
In the case of a server for the prtocol Echo, the class
will also contain a ByteBuffer
and a
boolean
. The boolean is used to
remember whether the client has closed the connection for writing.
The class Context
will implement the methods
doRead
and doWrite
as well as updateInterestOps
.
The method updateInterestOps
will be called by doRead
and doWrite
,
and will be the sole responible for updating the interestOps
of the SelectionKey
.
In order to update the value of interestOps
, you will just consider the state of
the ByteBuffer
as well as the boolean
.
Convention: For the methods doRead
, doWrite
and updateInterestOps
we will suppose that the ByteBuffer
is in write-mode before and after the call to the method.
Starting from the draft ServerEcho.java, write a server for the protocol Echo in non-blocking mode.
Once your server seems to be working, test it with the jar ClientEchoSlow.jar, which loops the byte 1 and, in another thread, displays the number of bytes received. The client never stops.
The significnace of this client is that it reads "slowly", and so
it slows down the rythm of the writing of the server (due to the flow control
done by TCP). Consulting the CPU resources (top
in the command line)
consumed by your server, you should see that they are very limited.
If the server consumes lots of CPU, it could be due to the fact that it tries
to write when in fact it is not possible (so active waiting); the exact opposite
of the principle of non-blocking I/O.
% java fr.upem.net.tcp.nonblocking.ServerEcho 7777 % java -jar ClientEchoSlow.jar localhost 7777