Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / General / December 2006

Tip: Looking for answers? Try searching our database.

Non-Blocking Socket and BufferedInputStream

Thread view: 
mshetty@mail.com - 29 Dec 2006 11:23 GMT
Hi,

We are new to Java. We are using some exising code which does a
byte-by-byte read from the socket. The data that is being read is an
xml string. A byte-by-byte read takes about 150 to 200 milliseconds. We
need to improve the response time.

The application uses BufferedInputStream's read method to read the
data. On the net we found some sites which suggested doing read in
chunks (read an array of bytes).
Looping until read returns -1 does not work as read block which the
data read is complete.

As per the documentation the read API should return -1 if there is no
data.

Similarly DataInputStream's readFully also blocks.

Is there a non-blocking way of doing a read in Java?

Would help if you suggest some alternative.

Thanks and Regards,
M Shetty
Derek Tandy - 29 Dec 2006 13:27 GMT
Code Listing 18-4: NonBlockingServer.java

import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class NonBlockingServer
{
   public static void main(String[] args)
   {
       try
       {
           Selector selector = Selector.open();

           ServerSocketChannel serverSocketChannel =
               ServerSocketChannel.open();
           serverSocketChannel.configureBlocking(false);

           ServerSocket serverSocket = serverSocketChannel.socket();

           serverSocket.bind(new InetSocketAddress(9000));

           System.out.println("Non-blocking Server created on port
               9000");

           serverSocketChannel.register(selector,
               SelectionKey.OP_ACCEPT);

           System.out.println("Waiting for client connections...");

           int amountToProcess = 0;
           while(true)
           {
               amountToProcess = selector.selectNow();

               if(amountToProcess > 0)
               {
                   try
                   {
                       Set keys = selector.selectedKeys();

                       Iterator iterator = keys.iterator();

                       while(iterator.hasNext())
                       {
                           SelectionKey selectionKey =
                               (SelectionKey) iterator.next();
                           iterator.remove(); // remove the key

                           int operation = selectionKey
                               .interestOps();

                           if((SelectionKey.OP_ACCEPT & operation)
                               != 0)
                           {
                               // Accept the connection...
                               ServerSocketChannel channel =
                                   (ServerSocketChannel)
                                   selectionKey.channel();
                               SocketChannel socket =
                                   channel.accept();
                               socket.configureBlocking(false);

                               // register for a writing operation
                               socket.register(selector,
                                   SelectionKey.OP_WRITE);

                               System.out.println("Client
                                   Connected...");
                           }
                           else if((SelectionKey.OP_READ &
                               operation) != 0)
                           {
                               // Attempt to read...
                               System.out.println("About to read
                                   from client...");

                               SocketChannel socket =
                                  (SocketChannel) selectionKey
                                  .channel();

                               // get the message from the client...
                               ByteBuffer incomingLengthInBytes =
                                   ByteBuffer.allocate(4);
                               // size of an 'int'
                               socket.read(incomingLengthInBytes);
                               incomingLengthInBytes.rewind();
                               int incomingLength =
                                   incomingLengthInBytes.getInt();
                               System.out.println("Got Incoming
                                   Length as: "+incomingLength+"
                                   bytes");

                               // now allocate the correct size for
                               // the message...
                               ByteBuffer incomingData = ByteBuffer
                                   .allocate(incomingLength);
                               socket.read(incomingData);
                               incomingData.rewind();
                               String string = incomingData
                                   .asCharBuffer().toString();

                               // Finally print received message...
                               System.out.println("Received:
                                   "+string);

                               // terminate the connection...
                               socket.close();
                           }
                           else if((SelectionKey.OP_WRITE &
                               operation) != 0)
                           {
                               // Attempt to write...

                               System.out.println("Now going to
                                   write to client...");

                               SocketChannel socket =
                                   (SocketChannel) selectionKey
                                   .channel();

                               socket.register(selector,
                                   SelectionKey.OP_READ);

                               String stringToSend = "This is a
                                   message";

                               int length = stringToSend.length()
                                   * 2;

                               ByteBuffer lengthInBytes =
                                   ByteBuffer.allocate(4);
                                   // 4 = size of a 'int'
                               ByteBuffer dataToSend =
                                   ByteBuffer.allocate(length);

                               lengthInBytes.putInt(length);
                               lengthInBytes.rewind();
                               dataToSend.asCharBuffer()
                                   .put(stringToSend);

                               ByteBuffer sendArray[] =
                                   {lengthInBytes, dataToSend};

                               socket.write(sendArray);
                               //socket.close();
                               System.out.println("Sent Message to
                                   Client...");
                           }
                       }
                   }
                   catch(IOException e)
                   {
                       System.out.println(e);
                   }
               }
           }
       }
       catch(IOException e)
       {
           System.out.println(e);
       }

   }
}

Code Listing 18-5: NonBlockingClient.java

import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;

public class NonBlockingClient
{
   public static void main(String[] args)
   {
       try
       {
           SocketChannel socketChannel = SocketChannel.open();
           socketChannel.connect(new InetSocketAddress("127.0.0.1",
               9000));

           // wait for the message from the server...
           ByteBuffer incomingLengthInBytes =
               ByteBuffer.allocate(4); // size of an 'int'
           socketChannel.read(incomingLengthInBytes);
           incomingLengthInBytes.rewind();
           int incomingLength = incomingLengthInBytes.getInt();
           System.out.println("Got Incoming Length as:
               "+incomingLength+" bytes");

           // now allocate the correct size for the message...
           ByteBuffer incomingData =
               ByteBuffer.allocate(incomingLength);
           socketChannel.read(incomingData);
           incomingData.rewind();
           String string = incomingData.asCharBuffer().toString();

           // Finally print the received message...
           System.out.println("Received: "+string);

           // Send a message back to the server...
           String replyMessage = "Message Received - Thank you!";
           int length = replyMessage.length() * 2;

           ByteBuffer replyLength = ByteBuffer.allocate(4);
           replyLength.putInt(length);
           replyLength.rewind();

           ByteBuffer replyText = ByteBuffer.allocate(length);
           replyText.asCharBuffer().put(replyMessage);

           ByteBuffer toSend[] = {replyLength, replyText};
           socketChannel.write(toSend);

       }
       catch(IOException e)
       {
           System.out.println(e);
       }

   }
}

On Dec 29, 5:23 am, mshe...@mail.com wrote:
> Hi,
>
[quoted text clipped - 20 lines]
> Thanks and Regards,
> M Shetty
EJP - 30 Dec 2006 01:32 GMT
>                                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.
Knute Johnson - 29 Dec 2006 23:21 GMT
> Hi,
>
> We are new to Java. We are using some exising code which does a
> byte-by-byte read from the socket. The data that is being read is an
> xml string. A byte-by-byte read takes about 150 to 200 milliseconds. We
> need to improve the response time.

How much data are you reading in 200 ms?

> The application uses BufferedInputStream's read method to read the
> data. On the net we found some sites which suggested doing read in
> chunks (read an array of bytes).
> Looping until read returns -1 does not work as read block which the
> data read is complete.

The -1 is for when the end of stream has been reached, for example you
attempt to read one more byte than exists in a file.  You said you were
reading from a socket?  Is the server closing when you've sent all the
data you want to send?  If it is you will throw an IOException in the
read and -1 never comes into play.

> As per the documentation the read API should return -1 if there is no
> data.

Probably not.

> Similarly DataInputStream's readFully also blocks.
>
> Is there a non-blocking way of doing a read in Java?
>
> Would help if you suggest some alternative.

Can you do any more work if you don't have all the data?  If not I would
just play with the buffer size of the BufferedInputStream.  It is
capable of very fast I/O.  What is the size of the buffer now?

Signature

Knute Johnson
email s/nospam/knute/

Daniel Pitts - 30 Dec 2006 18:42 GMT
> Hi,
>
[quoted text clipped - 20 lines]
> Thanks and Regards,
> M Shetty

There are two approaches... Using NIO, or using Threads.

I would suggest starting with the Threads approach, it is a lot more
straight forward.

Good luck.


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.