0

I'm trying to implement a simple client-server application, using NIO.

As an exercise, communication should be text-based and line-oriented. But when the server reads the bytes sent by the client, it gets nothing, or rather, the buffer is filled with a bunch of zeroes.

I'm using a selector, and this is the code triggered, when the channel is readable.

private void handleRead() throws IOException {
    System.out.println("Handler Read");
    while (lineIndex < 0) {
        buffer.clear();
        switch (channel.read(buffer)) {
            case -1:
            // Close the connection.
            return;

            case 0:
            System.out.println("Nothing to read.");
            return;

            default:
            System.out.println("Converting to String...");
            buffer.flip();
            bufferToString();
            break;
        }
    }
    // Do something with the line read.
}

In this snippet, lineIndex is an int holding the index at which the first \n occurred, when reading. It is initialized with -1, meaning there's no \n present.

The variable buffer references a ByteBuffer, and channel represents a SocketChannel.

To keep it simple, without Charsets and whatnot, this is how bufferToString is coded:

private void bufferToString() {
    char c;
    System.out.println("-- Buffer to String --");
    for (int i = builder.length(); buffer.remaining() > 1; ++i) {
        c = buffer.getChar();
        builder.append(c);
        System.out.println("Appending: " + c + "(" + (int) c + ")");
        if (c == '\n' && lineIndex < 0) {
            System.out.println("Found a new-line character!");
            lineIndex = i;
        }
    }
}

The variable builder holds a reference to a StringBuilder.

I expected getChar to do a reasonable convertion, but all I get in my output is a bunch (corresponding to half of the buffer capacity) of

Appending: (0)

Terminated by a

Nothing to read.

Any clues of what may be the cause? I have similar code in the client which is also unable to properly read anything from the server.


If it is of any help, here is a sample of what the writing code looks like:

private void handleWrite() throws IOException {
    buffer.clear();
    String msg = "Some message\n";
    for (int i = 0; i < msg.length(); ++i) {
        buffer.putChar(msg.charAt(i));
    }
    channel.write(buffer);
}

I've also confirmed that the result from channel.write is greater than zero, reassuring that the bytes are indeed written and sent.

afsantos
  • 5,178
  • 4
  • 30
  • 54
  • hmm. How do you know your server and client are well-connected and actually transferring data to each other? – yair Oct 13 '13 at 01:10
  • 1. Why are you terminating at buffer.remaining() == 1? 2. Why aren't you using a CharSet to decode the buffer? – user207421 Oct 13 '13 at 01:13
  • @yair Is there any chance of them not being connected, and the `Selector` triggering an `isReadable` on the socket that represents the client? – afsantos Oct 13 '13 at 01:19
  • @EJP **1.** I'm converting bytes to characters, and the documentation says *`BufferUnderflowException` - If there are fewer than two bytes remaining in this buffer*, so I'm avoiding a `try-catch`. **2.** Since I'm using Java and basic characters only on both sides, I figured I'd give `getChar` and `putChar` a try first, but it seems it is a better time investment to go with `Charset`s. – afsantos Oct 13 '13 at 01:23
  • @afsantos That suggestion doesn't begin to make sense. A socket can't become readable unless it is connected. – user207421 Oct 13 '13 at 01:24

1 Answers1

3

Turns out, this was a buffer indexing problem. In the server, a flip() was missing before writing to the socket. In the client code, a few flip() were missing too, after reading and before writing. Now everything works as expected.

Current writing code (server side):

private void handleWrite() throws IOException {
    String s = extractLine();
    for (int i = 0, len = s.length(); i < len;) {
        buffer.clear();
        while (buffer.remaining() > 1 && i < len) {
            buffer.putChar(s.charAt(i));
            ++i;
        }
        buffer.flip();
        channel.write(buffer);
    }
    // some other operations...
}
afsantos
  • 5,178
  • 4
  • 30
  • 54
  • You'll find your life is simpler if you maintain `ByteBuffers` in a state ready for reading: flip() them immediately before writing, and compact() them immediately afterwards. – user207421 Oct 17 '13 at 07:37
  • @EJP I hadn't seen `compact` yet, thanks for the tip. What you suggest is even in the documentation for `compact`, as an example. – afsantos Oct 17 '13 at 08:31