Synchronous IO with nio

Hello,

I have been programming in Java for a while but today is my first shot at NIO.

I'm implementing a custom protocol over TCP between a java server and several Java and C clients. The classical one-thread-per-client approach works with a couple of test connections, but I'm afraid it will not scale:

- I will not have control on all clients programs

- I don't know yet the expected traffic

- I might introduce bugs as well in the sever program and leak sockets and threads

Here is the code for the classical approach:

ServerSocket server.accept() = ...;

Socket client = server.accept();

Thread clientThread =new ClientThread(client);

clientThread.start();

Where ClientThread's run method deos the following:

BufferedReader reader =new BufferedReader(new InputStreamReader(client.getInputStream()));

while (isConnected){

String pdu = reader.readLine();

storeClientInfo(pdu);

}

The key is that in order to make for a very simple protocol we designed it so that each PDU is one line of text, sent over TCP. Reading one PDU is therefiore a synchronous operation.

In order to provision scaling to many clients, we are trying to reimplement it using NIO.

We use a Selector to wait for requests, and once accepted, we register the Selector with the created SocketChannel (one per client).

One single thread dequeues reads selected by the Selector, but we are still trying to read using the synchronous BufferredReader.

When a SocketChannel has readable data, we create a Buffered reader like this:

BufferedReader reader =new BufferedReader(new InputStreamReader(socketChannel.socket().getInputStream()));

String pdu = reader.readLine();// throws IllegalBlockingModeException

}

And as you probably expect, we get an IllegalBlockingModeException when reading the line (synchronous operation) over the (asynchronous) socket.

If my understanding is correct, the SocketChannel has to be put in non-blocking mode to be selectable by the Selector (otherwise the register(...) call throws anIllegalBlockingStateException), but it cannot be read synchronously in this non-blocking mode.

Is there any way I can connect a BufferedReader to a non-blocking SocketChannel somehow?

There would be an alternative approach, where the listeing thread reads the data from the SocketChannel into a ByteBuffer, then stuffs those bytes into a PipedOutputStream, and build a reader over a PipedInputStream.

The problem is that in this case, we need, again, one thread per client to read the data froml the reader...

There's a third approach, where we would use the "attachment" feature of SocketChannel.register() and SelectedKey; in this approach, the attachment could be a stringBuffer; each time the Selector warns us that data is readable, the data would be read from the SocketChannel into a ByteBuffer. then added to the StringBuffer. We would have to analyze the StringBuffer's content to find out whether it contains a whole line, and only then extract this line as the "PDU".

Is this a recommended approach?

Thanks in advance, and regards,

J.

Message was edited by: jduprez (some code markers had been swallowed)

Message was edited by:

jduprez

[3691 byte] By [jdupreza] at [2007-11-26 18:41:39]
# 1

Your third approach is about the only viable one.

You need to firmly forget all about streams when in non-blocking mode, and you have to reorient your request-parsing strategy into a 'push-parser' where new data is pushed into the parser rather than the parser pulling data from the stream.

And you have the problem of 'is the request complete yet?': if not just let the input keep accumulating; when it is complete you have to clear the stringbuffer and then take action on the request. At this point you will probably hand the request off to a pool of worker threads somehow.

Everything gets kind of upside-down.

ejpa at 2007-7-9 6:15:39 > top of Java-index,Archived Forums,Socket Programming...
# 2

OK, thanks.

I implemented this third approach, and it works under moderate load. The difficulty though, is that what I can read from the channel is a chunk of binary data, possibly incomplete, and that possibly doesn't even translate to valid chars (for example, a 16-bits char may be cut in-between and I get only the first byte).

That orients me to a binary protocol (at least, to a protocol where I can identify "end of message " markers at the binary level).

Alternately, if I can ensure all my messages fit a UDP packet, I'd better stuff the String in a UDP packet on the client end, and decode a String from the UDP received (atomically) on the server end.

I'd lose TCP's relative reliability, but simplify the programming model for the protocol handling...

I'm still free to choose the transport at this step of the project...

Any recommendation?

jdupreza at 2007-7-9 6:15:39 > top of Java-index,Archived Forums,Socket Programming...