Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Java NIO Socket Programming

Tech May 18 4

Server:

public class NIOServer {
    private static final String HOST = "localhost";
    private static final int PORT = 10086;

    public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;
        Selector selector = null;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(HOST, PORT));
            serverSocketChannel.configureBlocking(false);
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                if (selector.select() == 0) {
                    continue;
                }
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while (it.hasNext()) {
                    SelectionKey selectionKey = it.next();
                    if (selectionKey.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                        SocketChannel socketChannel = ssc.accept();
                        if (socketChannel == null) {
                            continue;
                        }
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(4 * 1024);
                        int bytesRead = sc.read(buffer);
                        if (bytesRead == -1) {
                            selectionKey.cancel();
                            sc.close();
                        } else {
                            StringBuilder data = new StringBuilder();
                            while (bytesRead > 0) {
                                buffer.flip();
                                data.append(new String(buffer.array()));
                                buffer.clear();
                                bytesRead = sc.read(buffer);
                            }
                            System.out.println("Server received: " + data.toString());
                            sc.register(selector, SelectionKey.OP_WRITE);
                        }
                    } else if (selectionKey.isWritable()) {
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        String response = "Server response: " + Math.random();
                        ByteBuffer sendBuffer = ByteBuffer.wrap(response.getBytes());
                        while (sendBuffer.hasRemaining()) {
                            sc.write(sendBuffer);
                        }
                        sc.register(selector, SelectionKey.OP_READ);
                        System.out.println(response);
                    }
                    it.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

public class NIOClient {
    public static void main(String[] args) throws Exception {
        SocketChannel clientChannel = SocketChannel.open();
        clientChannel.configureBlocking(false);
        if (!clientChannel.connect(new InetSocketAddress("localhost", 10086))) {
            while (!clientChannel.finishConnect()) {
                System.out.print(".");
            }
        }
        System.out.print("\n");
        ByteBuffer writeBuffer = ByteBuffer.wrap("Hello, Server!".getBytes());
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);

        while (writeBuffer.hasRemaining()) {
            clientChannel.write(writeBuffer);
        }

        Thread.sleep(10000);
        StringBuilder receivedData = new StringBuilder();
        while ((clientChannel.read(readBuffer)) > 0) {
            readBuffer.flip();
            receivedData.append(new String(readBuffer.array(), 0, readBuffer.limit()));
            readBuffer.clear();
        }
        System.out.println("Client Received: " + receivedData.toString());
        clientChannel.close();
    }
}

Note that when the client disconnects, the serve'rs corresponding channel should be handled properly to avoid a continuous readable state, which can cause an infinite loop.

When the client calls clientChannel.close(), it sends a close signal to the server. The server's socketChannel.read(byteBuffer) will return -1, indicating the close signal. However, if the byte buffer is full, it may return 0 instead of -1. You should handle this based on your specific use case.

In non-blocking mode, read can return -1 or 0 under different conditions. Here are some key points:

  • When read returns -1: This indicates that the client has finished sending data and closed the connection. The server should close the socketChannel and cancel the SelectionKey.
  • When read returns 0: This can happen in three scenarios:
  1. There is no data to read at the moment.
  2. The ByteBuffer's position equals its limit.
  3. The cliant has finished sending data and is waiting for a response.

If the client does not close the channel after sending data, and the server sends a responce, the client's read method may continuously return 0.

Handling OP_WRITE Events:

  • OP_WRITE events are ready when there is space in the underlying buffer. Registering OP_WRITE can cause the event to be continuously ready, consuming CPU resources. Only register OP_WRITE when you have data to write, and deregister it after writing.
  • Example of handling OP_WRITE:
while (buffer.hasRemaining()) {
    int len = socketChannel.write(buffer);
    if (len == 0) {
        selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
        selector.wakeup();
        break;
    }
}

  • After writing, deregister OP_WRITE:
selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);

Each SocketChannel corresponds to one SelectionKey, so use key.interestOps() to manage event registration and deregistration.

Example of writing data using a buffer and interestOps:

public void write(MessageSession session, ByteBuffer buffer) throws ClosedChannelException {
    SelectionKey key = session.key();
    if ((key.interestOps() & SelectionKey.OP_WRITE) == 0) {
        key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
    }
    writebuf.put(buffer);
    selector.wakeup();
}

while (true) {
    selector.select();
    // ...
    if (key.isWritable()) {
        MessageSession session = (MessageSession) key.attachment();
        synchronized (session) {
            writebuf.flip();
            SocketChannel channel = (SocketChannel) key.channel();
            int count = channel.write(writebuf);
            writebuf.clear();
            key.interestOps(SelectionKey.OP_READ);
        }
    }
    // ...
}

Key Points:

  • Use buffers and interestOps to manage data writing.
  • Each SocketChannel has one SelectionKey, so use interestOps to change event registrations.
Tags: JavaNIO

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.