> SocketChannel socket =
> channel.accept();
> socket.configureBlocking(false);
OK so far ...
> // register for a writing operation
> socket.register(selector,
> SelectionKey.OP_WRITE);
Why? OP_WRITE is almost always true. This will cause a hard loop in the
selection loop. Usually a server will register for OP_READ at this point.
> // get the message from the client...
> ByteBuffer incomingLengthInBytes =
> ByteBuffer.allocate(4);
This is wasteful. You're always going to need this buffer, allocate it once.
> // size of an 'int'
> socket.read(incomingLengthInBytes);
At this point you are blindly assuming that you've read all 4 bytes. And
you've ignored the possible cases of reading 0 bytes or EOF.
> // now allocate the correct size for
> // the message...
Again this is wasteful. Why not just allocate a ByteBuffer large enough
for any message? and then you can do both reads at the same time, into
the same buffer even.
> ByteBuffer incomingData = ByteBuffer
> .allocate(incomingLength);
> socket.read(incomingData);
At this point you've again blindly assumed that you've read the entire
message. And again you've ignored the possible cases of reading 0 bytes
or EOF.
None of this is good enough. You have to read until the message is
complete in both cases. You have to cope with a read count of zero. You
have to cope with EOF, premature or otherwise.
> incomingData.rewind();
> String string = incomingData
[quoted text clipped - 3 lines]
> System.out.println("Received:
> "+string);
Received something you mean. There's no guarantee that it's even
complete enough to decode successfully to a String.
> // terminate the connection...
> socket.close();
...
> socket.write(sendArray);
And again you've ignored the return code so you can't detect a short
write or a full send buffer.
> socketChannel.read(incomingLengthInBytes);
Again you've ignored the return value, see above.
> socketChannel.read(incomingData);
And again ...
> socketChannel.write(toSend);
And again ...
I'm sorry but this code is not good enough for serious use.
Richard Maher - 31 Dec 2006 00:16 GMT
Hi,
> Again this is wasteful. Why not just allocate a ByteBuffer large enough
> for any message? and then you can do both reads at the same time, into
> the same buffer even.
Maybe not in this simple case, but one reason for not combining the reads
into a "maxi" buffer would be that you'd possibly retrieve multiple messages
rather than just the first one you're interested in. Although, I certainly
agree with allocating the buffer at the start.
Anyway, sorry for the interjection but here are some newbie questions if
anyone has the time or inclination: -
1) Is it correct that NIO and Channels are the preferred, and more
efficient, options for Java Socket communication these days? (As opposed to
IO and Net.Socket classes) Other Buffers and read() methods seem to imply
loops round single-byte reads, and I've never really understood mark(), so
I'm quite taken by the look of this example.
2) If I go the "Preallocate a maxi-buff at initialize time" route, (and I
personally have a 2-byte message header denoting an implicit length) would
it sound reasonable to do a .read(headerbuff) and if it's a type "A1" record
then databuff.limit(a1length) before the subsequent .read(databuff)
3) Why in the Derek's example does the length have to be multiplied by 2
each time? Surely bytes is bytes is bytes?
4) It looks like an operation on a buffer/bytebuffer is dependent on the
current values of position and limit (and capacity obviously). Is this true,
and is this why you must always rewind() the buffer after the socket read
before doing any work on it?
5) When does a buffer get marked for removal or garbage collected?
6) I really like the simplicity of the following: -
> ByteBuffer toSend[] = {replyLength, replyText};
> socketChannel.write(toSend);
Is there an equvilently easy Unstring opertaion for plucking fields out of a
read network message, or is it better/de rigueur to to use the
GETprimative() methods to obtain each field from the buffer in turn? Given
that things like Endianness and Character-set seem to be properties of
Buffers then maybe the second strategy makes more sense.
Any help (or other examples) greatly appreciated.
Regards Richard Maher
PS.
> I'm sorry but this code is not good enough for serious use.
Esmond, I believe you've written books on this and I'm sure they're readily
available, but it you'd like to provide an example here of code that *is*
good enough for serious use here then I, for one, would certainly like to
see it! And as someone who grew up in salubrious E.Coburg and now resides in
Perth, "Your weather's rubbish!" :-)
> > SocketChannel socket =
> > channel.accept();
[quoted text clipped - 74 lines]
>
> I'm sorry but this code is not good enough for serious use.
EJP - 31 Dec 2006 05:30 GMT
> Maybe not in this simple case, but one reason for not combining the reads
> into a "maxi" buffer would be that you'd possibly retrieve multiple messages
> rather than just the first one you're interested in.
There's nothing wrong with that, as a strategy.
> 1) Is it correct that NIO and Channels are the preferred, and more
> efficient, options for Java Socket communication these days?
Not really. Only if you need about 20% extra performance and/or the
extra facilities such as multiplexing or NBIO, or the ability to
transfer between channels. Using NIO is a major design decision as it
affects everything, don't enter on it lightly. For example I would
rarely if ever use it at a client.
> 2) If I go the "Preallocate a maxi-buff at initialize time" route, (and I
> personally have a 2-byte message header denoting an implicit length) would
> it sound reasonable to do a .read(headerbuff) and if it's a type "A1" record
> then databuff.limit(a1length) before the subsequent .read(databuff)
Yes.
> 3) Why in the Derek's example does the length have to be multiplied by 2
> each time? Surely bytes is bytes is bytes?
Because he's converting Java chars to bytes, although again this is not
the correct way to do it: there are java.nio.charset methods for that.
> 4) It looks like an operation on a buffer/bytebuffer is dependent on the
> current values of position and limit (and capacity obviously). Is this true,
> and is this why you must always rewind() the buffer after the socket read
> before doing any work on it?
Yes, or clear it, or best of all consume the data with compact() - this
lets you cope with any read-aheads that may have happened.
> 5) When does a buffer get marked for removal or garbage collected?
Same as any other Java object, except for FileChannel mapped buffers
which are never collected (grrr).
> 6) I really like the simplicity of the following: -
>
[quoted text clipped - 6 lines]
> that things like Endianness and Character-set seem to be properties of
> Buffers then maybe the second strategy makes more sense.
There are scattering-read methods corresponding to these gathering-write
methods. Very useful when you have a fixed-length header, message, and
trailer. In the case of a length word you generally need the length
value before you read the content, so you don't use them for that.