Search code examples
javamultithreadingnio

NIO Thread CPU usage


I am running into CPU usage problem when I am using a java.nio.channel.Selector. when the server thread started, it initially consume 200% cpu resource and dramatically drop down to 0.1%. but if it is connected by a client. this number rapidly increases to 97% - 100% and keep that number even after the client disconnected.

here is the code I wrote for server.

package com.cs.gang.test;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public final class TCPConnectionServer implements Runnable {

private RandomAccessFile logFile;
private Selector selector;

public TCPConnectionServer() throws IOException {
    final File logFile = new File("server_log.txt");
    if (logFile.exists()) {
        logFile.delete();
    }

    this.logFile = new RandomAccessFile(logFile.getCanonicalPath(), "rw");
    System.out.println(logFile.getCanonicalPath());

    selector = Selector.open();
    final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.configureBlocking(false);
    serverSocketChannel.socket().bind(new InetSocketAddress(8888));
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

}

@Override
public void run() {
    while (true) {
        try {
            if (selector.select() > 0) {
                final Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                while (keys.hasNext()) {
                    final SelectionKey key = keys.next();
                    keys.remove();

                    if (key.channel() instanceof SocketChannel) {
                        if (!((SocketChannel) key.channel()).isConnected()) {
                            logFile.writeChars(((SocketChannel) key.channel()).toString() + " is off line");
                        }
                    }

                    if (key.isAcceptable()) {
                        final ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                        final SocketChannel clientChannel = serverSocketChannel.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                        logFile.writeChars(clientChannel.toString() + "is now connected");
                    } else if (key.isReadable()) {

                        final SocketChannel client = (SocketChannel) key.channel();
                        if (client.isConnected()) {
                            final ByteBuffer buffer = ByteBuffer.allocate(1024);
                            int byteRead = -1;
                            final StringBuilder sb = new StringBuilder(client.toString()).append(" : ");

                            while ((byteRead = client.read(buffer)) > 0) {
                                sb.append(new String(buffer.array()), 0, byteRead);
                                buffer.clear();
                            }
                            logFile.writeChars(sb.toString());
                            System.out.println(sb.toString());
                        } else {
                            System.out.println("Closed Connection detected");
                        }

                    }
                }
            } else {
                System.out.println("Sleep for 100ms");
                Thread.sleep(100);
            }
        } catch (final IOException e) {
            e.printStackTrace();
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) throws IOException {
    new Thread(new TCPConnectionServer()).start();
}
}

can any one help me out? I am new to NIO and I am having absolutely no idea about this problem now.

Thanks


Solution

  • clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    

    The problem is here. OP_WRITE is almost always ready, so your selector will rarely block and usually spin. This is a misuse of OP_WRITE. The correct way to use it is as follows:

    1. Write whenever you have something to write.
    2. If write() returns zero, register the socket for OP_WRITE and return to the selection loop. Of course you'll also have to save the ByteBuffer you were writing from, in association with the channel: this is normally done via the SelectionKey's attachment, either directly or indirectly. Ideally you will have both a read and a write ByteBuffer per channel, saved in a channel context object which in turn is saved as the key attachment.
    3. When OP_WRITE fires, continue writing from that ByteBuffer. If this completes, i.e. write() does't return zero or a short write count, de-register the channel for OP_WRITE.