Иван Петров
Иван Петров

Reputation: 53

Many issues with reading and writing in NIO

I'm practicing with NIO and trying to make a simple app with client and server sides. This app should just send message in bytes from clien to server and get other message as response. My code is down here. But I have a lot of different issues.

Sometimes lines int bytesRead = socketChannel.read(byteBuffer); or bytesRead = socketChannel.read(byteBuffer); from method readMessage() reads endless sequence of zero bytes and throws OOM error.

Sometimes response from server looks like that instead of {"class":"server.PasswordHashResponse","xoredHash":"RV5GX1JVAwADBEVZWwFGTAhZQ1FGX1tYQ11ZVwA\u003d"}.

Sometimes response has odd tail like this: {"class":"server.PasswordHashResponse","xoredHash":"RV5GX1JVAwADBEVZWwFGTAhZQ1FGX1tYQ11ZVwA\u003d"}YQ11ZVwA\u003d

Both server and cliend use same methods for reading and writing. I send from client {"class":"server.PasswordHashRequest","login":"admin"} and expect {"class":"server.PasswordHashResponse","xoredHash":"RV5GX1JVAwADBEVZWwFGTAhZQ1FGX1tYQ11ZVwA\u003d"}. With the same code I can get one issue now and other issue few minutes later. I've tried anything I know. Did I managed to get segfault in Java?

Client side code:

    @Test
public void main() throws Exception {
    System.out.println("Opening socket");
    InetSocketAddress socketAddress = new InetSocketAddress("localhost", 9090);
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
    Selector selector = Selector.open();
    socketChannel.register(selector, OP_CONNECT);
    socketChannel.connect(socketAddress);
    PasswordHashRequest request = new PasswordHashRequest("admin");
    System.out.println("Socket open");
    while (true) {
        System.out.println("Client selector awoken");
        selector.select();
        for (SelectionKey selectionKey : selector.selectedKeys()) {
            if (selectionKey.isConnectable()) {
                socketChannel.finishConnect();
                selectionKey.interestOps(OP_WRITE);
            } else if (selectionKey.isReadable()) {
                String response = ServerManager.readMessage((SocketChannel) selectionKey.channel());
                System.out.println(response);
                server.interrupt();
            } else if (selectionKey.isWritable()) {
                ServerManager.sendMessage(request, (SocketChannel) selectionKey.channel());
                System.out.println("Request sent");
                selectionKey.interestOps(OP_READ);
            }
        }
    }
}

Server side code:

    public void run() {
    System.out.println("Main thread started");
    while (true) {
        try {
            // Get ready channels
            int readyChannels = selector.select();
            if (readyChannels == 0) { continue; }

            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

            // Handle Events
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();

                // New Client
                if (key.isAcceptable()) {
                    System.out.println("New Client Accepted");
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    serverSocketChannel.configureBlocking(false);

                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    SelectionKey clientKey = socketChannel.register(selector, SelectionKey.OP_READ);
                    Random randomInt = new Random(System.currentTimeMillis());
                    clientKey.attach(randomInt.nextInt(Integer.SIZE - 1));
                }
                // Client has sent data
                else if (key.isReadable()) {
                    handleInput(key);
                }

                keyIterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Read method:

    public static String readMessage(SocketChannel socketChannel) throws IOException {
    ByteBuffer byteBuffer = ByteBuffer.allocate(16);
    byteBuffer.clear();
    StringBuilder stringBuilder = new StringBuilder();
    int bytesRead = socketChannel.read(byteBuffer);
    while (bytesRead != -1) {
        byteBuffer.flip();
        String byteString = new String(byteBuffer.array(), Charset.forName("UTF-8"));
        stringBuilder.append(byteString);
        byteBuffer.clear();
        bytesRead = socketChannel.read(byteBuffer);
    }
    socketChannel.shutdownInput();
    return stringBuilder.toString();
}

Write method:

    public static void writeMessage(String message, SocketChannel channel) throws IOException {
    message += "\r\n";
    System.out.println(message);
    int bufferLength = 16;
    byte[] responseBytes = message.getBytes();
    int offset = 0;
    ByteBuffer buf = ByteBuffer.allocate(bufferLength);
    while (responseBytes.length > offset) {
        buf.clear();
        int div = responseBytes.length - offset;
        if (div >= bufferLength) {
            buf.put(responseBytes, offset, bufferLength);
        } else {
            buf.put(responseBytes, offset, div);
        }
        buf.flip();
        channel.write(buf);
        offset += bufferLength;
    }
    channel.shutdownOutput();
}

Upvotes: 1

Views: 836

Answers (1)

user207421
user207421

Reputation: 310850

  • Your read method should stop reading if bytesRead <= 0
  • It should take account of the buffer limit when constructing the string
  • Your write method should stop trying write if write() returns zero, and then (and only then) register the channel for OP_WRITE, and only continue writing when it fires.

See many similar questions here on all this.

Upvotes: 2

Related Questions