tags:

views:

961

answers:

3

I'm having to modify some client side code because the comms protocol wasn't defined properly.

I'd assummed that a tcp message from the server would terminate in a new line so I used reader.readLine() to read my Data.

Now I've been told that this is not the case and that instead the first 4 chars of the message are the message length and then I have to read the rest of the message.

What is the most efficient sensible way to do this?

my general Idea was as follows:

  1. Create a 4 char array
  2. Read in first 4 characters
  3. Determine what the message length is
  4. Create a new array of message length
  5. read into the new array.

Here is an example of the code (reader is a BufferedReader created elsewhere):

char[] chars = new char[4];
int charCount = reader.read(chars);
String messageLengthString = new String(chars);
int messageLength = Integer.parseInt(messageLengthString);
chars = new char[messageLength];
charCount = reader.read(chars);
if (charCount != messageLength)
{
    // Something went wrong...
}

I know how to do this, but Do I have to worry about the character buffers not being filled? if so how should I deal with this happening?

+1  A: 

Chars in Java are for text data. Are you sure the protocol really defines the length of the message this way? More likely it's the first four bytes to represent a 32-bit length.

If you're talking to C or C++ developers they may be using "char" as a synonym for "byte".

EDIT: Okay, based on the comment:

I would create a method which took a Reader and a count and repeatedly called read() until it had read the right amount of data, or threw an exception. Something like this:

public static String readFully(Reader reader, int length) throws IOException
{
    char[] buffer = new char[length];
    int totalRead = 0;
    while (totalRead < length)
    {
        int read = reader.read(buffer, totalRead, length-totalRead);
        if (read == -1)
        {
            throw new IOException("Insufficient data");
        }
        totalRead += read;
    }
    return new String(buffer);
}

Then your code can be:

String lengthText = readFully(reader, 4);
int length = Integer.parseInt(lengthText);
String data = readFully(reader, length);
// Use data now

You should check what happens when they want to send fewer than 1000 (or more than 9999) characters though...

Jon Skeet
It's a java programmer on the other end I think. The message is read as string. and the first 4 chars of the string denote the message length.I'm not saying it's logical or makes sense just that this is how I've been told the protocol works...
Omar Kooheji
Thanks Jon, it's zero padded so fewer than 1000 shouldn't be an issue and the message I'm dealing with is an ACK for a message I've sent so I don't really see it going over 9999 chars.
Omar Kooheji
A: 

Uh ... Isn't a char in Java 16 bits, for Unicode? I don't think you're doing the right thing, using chars to represent bytes coming off a network. You should probably look into using something like ByteBuffer from the java.nio package instead.

If you know the maximum size of a single message, there's no stopping you from just creating a single buffer, read four bytes into the buffer, parsing them out into a int or so, then doing a new read with that size, overwriting the buffer's contents.

UPDATE: The above assumed the protocol was binary, and that the use of char was a "C-ism". If the protocol is actually text, and the initial 4-char length is a padded integer (in some base, I'm guessing 10?) like "0047" or "6212", then some other approach is probably better, to not have to go from bytes to chars.

unwind
I've not defined the protocol otherwise the first 4 "bytes" would just be a 32 bit int allowing for the message to be millions of characters in length. As it is I'm working within the parameters I've been given...
Omar Kooheji
+1  A: 

Relating to the part of the question where you need to read a certain number of characters once you've established what this is, the following idiom is common with java.io.Readers:

int lengthToRead = getRequiredReadLength(); // Left as exercise to reader :-)
char[] content = new char[lengthToRead]
int from = 0;
while (lengthToRead > 0)
{
   try
   {
      int nRead = reader.read(context, from, lengthToRead);
      if (nRead == -1)
      {
         // End of stream reached before expected number of characters
         // read so handle this appropriately - probably throw an exception
      }
      lengthToRead -= nRead;
      from += nRead;
   }
   catch (IOException e)
   {
      // Handle exception
   }
}

Since the read call is guaranteed to return a non-zero result (the call blocks until some data is available, the end of the stream is reached (returns -1) or an exception is thrown) this while loop ensures you'll read as many characters as you need so long as the stream can supply them.

In general, whenever more than one character is asked for at once from a Reader one should be aware there's no guarantees that that many characters were actually supplied, and the return value should always be inspected to see what happened. Otherwise you'll inevitably end up with bugs at some point, where parts of your stream "disappear".

Andrzej Doyle