0

I am a total noob at Java, so apologies if this is a rookie mistake. I was trying out Java NIO, I still haven't gotten to a stage where I am using the non-blocking feature. I just am not able to get the server to read a string, I understand its not easy to send a Bytebuffer from one side and trying to interpret it as String on the other side, but i still can't figure out where I am going wrong. Here is the code

*****************************SERVER SIDE**************************************

class MyBlockingServer extends Thread
{
    private int M_PortNumber;
    private ServerSocket M_ServerSocket;

    MyBlockingServer(int PortNumber)
    {
        M_PortNumber = PortNumber;
        try {
            M_ServerSocket = new ServerSocket(M_PortNumber);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void run()
    {
        int my_number = 0;
        while(true)
        {
            try {
                Socket Server = M_ServerSocket.accept();


                DataInputStream inputStream = new DataInputStream(Server.getInputStream());
                System.out.println("[SERVER]" +inputStream.readUTF());


                DataOutputStream outputStream = new DataOutputStream(Server.getOutputStream());
                outputStream.writeUTF("Thanks for connection, you suck tata" + " "+ my_number);

                my_number++;
                Server.close();

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    void socket_close()
    {
        try {
            M_ServerSocket.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

public class JavaBlocking
{

    public static void main(String []args)
    {
        MyBlockingServer Server = new MyBlockingServer(8000);
        try {
            Server.start();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

*******************************CLIENT SIDE*********************************

public class JavaChannels 
{

    public static void main(String []args)
    {
        SocketChannel client_channel = null;

        try {
            client_channel = SocketChannel.open();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("[Client] Socket channel open");

        try {
            client_channel.connect(new InetSocketAddress("127.0.0.1",8000));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("[Client] Socket channel connected");

        ByteBuffer my_buffer = ByteBuffer.allocate(48);
        my_buffer.clear();

        try {
            my_buffer.put("WHY_YOU_NO_WORK".getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e2) {
            // TODO Auto-generated catch block
            e2.printStackTrace();
        }

        my_buffer.flip();

        try {
            int bytes_written = client_channel.write(my_buffer);

            while(my_buffer.hasRemaining())
            {
                bytes_written = client_channel.write(my_buffer);
            }

            System.out.println("[Client] Wrote "+ bytes_written +" bytes");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("[Client] Socket channel write finished");

        my_buffer.clear();
        my_buffer.flip();


        try {
            client_channel.read(my_buffer);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        System.out.println("[Client] server says" + new String(my_buffer.array()));

        try {
            client_channel.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


}

The error i keep getting is

java.io.EOFException at java.io.DataInputStream.readFully(DataInputStream.java:197) at java.io.DataInputStream.readUTF(DataInputStream.java:609) at java.io.DataInputStream.readUTF(DataInputStream.java:564) at netty_tutorial.blocking.MyBlockingServer.run(JavaBlocking.java:39)

This somehow indicates to me that readUTF is not reading UTF format but something else.

In summary what i am doing is

Server --> ReadUTF

Client --> String -->Byte Array in UTF-8 --> ByteBuffer -->Write

As I am explicitly encoding the byte array into UTF-8. why can't readUTF read it?

Desert Ice
  • 4,461
  • 5
  • 31
  • 58
  • If you use readUTF, the other side must use writeUTF. Unless you study this peculiar format and follow the small print: two bytes length, then the characters, etc. etc. but not really converted to UTF-8 (Although this might not cause any problems if your unicode characters aren't very outlandish.) – laune Jan 23 '15 at 10:41
  • ByteBuffer does not actually have writeUTF method, any workarounds ? – Desert Ice Jan 23 '15 at 10:43
  • DataOutputStream has. – laune Jan 23 '15 at 10:44
  • I will try that out. Still I am little unclear on why exactly it is failing, if a string is converted to a byte buffer with encoding as UTF-8, shouldn't readUTF work? – Desert Ice Jan 23 '15 at 10:45
  • The length, a short up front. – laune Jan 23 '15 at 10:46

2 Answers2

3

The DataInput.readUTF method doesn't read a UTF-8 encoded string, it reads data in the specific format that DataOutput.writeUTF creates, which is similar to but not the same as true UTF-8:

  • it starts with a 16 bit unsigned integer giving the number of following bytes that make up the string
  • these following bytes are a modified form of UTF-8, where U+0000 is represented in two bytes rather than 1 (so the binary representation of the string cannot contain any 0 bytes) and supplementary characters above U+FFFF are represented as a surrogate pair, with the high and low surrogate encoded to UTF-8 separately in 3 bytes each (true UTF-8 would encode the whole supplementary code point in one go using a total of four bytes).

If you are writing true UTF-8 then you need to read true UTF-8, if you want to readUTF then you must writeUTF.

If you want to writeUTF to a ByteBuffer it's pretty straightforward to implement an OutputStream wrapper around the buffer, which you can in turn wrap in a DataOutputStream:

class ByteBufferBackedOutputStream extends OutputStream{
  ByteBuffer buf;
  ByteBufferBackedOutputStream( ByteBuffer buf){
    this.buf = buf;
  }
  public synchronized void write(int b) throws IOException {
    buf.put((byte) b);
  }

  public synchronized void write(byte[] bytes, int off, int len) throws IOException {
    buf.put(bytes, off, len);
  }

}

(source)

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
  • Thanks, your explanation made the most sense to me. Somehow the name of the function did not indicate this :). – Desert Ice Jan 23 '15 at 11:02
  • It is not the case that you must use `DataOutputStream.writeUTF` there is a number of was you can solve this, I have included the simplest in my answer. – Peter Lawrey Jan 23 '15 at 11:04
  • @PeterLawrey as long as you're not using any supplementary characters, yes. – Ian Roberts Jan 23 '15 at 11:06
  • @IanRoberts, changed the readUTF to read. Converted the resultant byte array to String, got it working now. Thanks – Desert Ice Jan 23 '15 at 11:09
  • If you write supplementary character using UTF-8 encoding readUTF will decode them correctly. If you write supplementary characters with DataOutputStream.writeUTF they won't be valid UTF-8. – Peter Lawrey Jan 23 '15 at 11:12
  • @PeterLawrey are you sure about that? Looking at the code for DataInputStream in my versions of Java (7u72 and 8u25) it can only handle up to three bytes per character. – Ian Roberts Jan 23 '15 at 11:17
  • @DesertIce Somehow the ***Javadoc*** of the method *does* indicate this. All of it. – user207421 Jan 26 '15 at 22:22
  • @EJP I just meant that the name was could have been more clear. Anyways the doc mentions this "Clears this buffer. The position is set to zero, the limit is set to the capacity, and the mark is discarded.". I don't see anything that says that the mode is changed to read – Desert Ice Jan 27 '15 at 07:24
1

You need to write the format DataInputStream expects.

e.g.

public static void writeUTF(ByteBuffer bb, String text) {
    byte[] bytes = text.getBytes("UTF-8");
    if (bytes.length > 1 << 16)
        throw new IllegalArgumentException();
    bb.putShort((short) bytes.length);
    bb.write(bytes);
}

Note: while writeUTF will write a \0 as two bytes instead of one, the readUTF will accept it as 1 or 2 bytes.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130