Backend Development 28 min read

Netty Core Concepts and NIO Fundamentals in Java

This article provides a comprehensive overview of Java network programming fundamentals, including socket basics, IO models (BIO, NIO, AIO), NIO core components such as Channel, Buffer, and Selector, and detailed example code for building NIO client‑server applications.

Wukong Talks Architecture
Wukong Talks Architecture
Wukong Talks Architecture
Netty Core Concepts and NIO Fundamentals in Java

Hello everyone, I'm Wukong~ This article presents core knowledge of Netty, recommended for bookmarking and gradual study.

1. Review of Network Programming Basics

1. Socket

Socket means "socket" in English and is a language‑independent standard; any language that supports network programming has it. In Linux, a socket is a special file type representing inter‑process network communication, essentially a pseudo‑file created by the kernel using buffers. Because it is a file, it can be referenced via a file descriptor.

Similar to a pipe, Linux wraps sockets as files to provide a unified interface, allowing read/write operations on sockets to be consistent with file I/O. The difference is that pipes are mainly for local IPC, while sockets are for network communication.

In simple terms, a Socket is an API that enables two applications on the network to exchange data through a bidirectional connection.

The basic flow of Socket communication is as follows:

(1) The server calls Listen to start listening for client connections.

(2) The client calls Connect to connect to the server; the server accepts the connection with Accept . During the connect‑accept process, a three‑way handshake occurs.

(3) The client and server use write and read to send and receive data; the OS handles TCP confirmation and retransmission.

(4) The connection is closed with close , performing a four‑way handshake.

In Java, the java.net package provides the basic networking classes. ServerSocket and Socket are the fundamental types. ServerSocket is the server‑side type, while Socket represents the established connection.

2. IO Model Introduction

For a single IO operation, data is first copied to kernel space, then from kernel space to user space, so a read operation involves two stages: (1) waiting for data to be ready, (2) copying data from kernel to user space. This yields five IO modes:

Blocking IO (BIO): the process blocks during both stages.

Non‑blocking IO (NIO): the process polls for readiness; the first stage is non‑blocking, the second blocks.

Multiplexed IO (select/poll): multiple connections share a selector that polls for readiness.

Signal‑driven IO: the process receives a notification when data is ready.

Asynchronous IO (AIO): both stages are non‑blocking; the OS notifies when data is ready.

Sync vs Async

Sync : the caller waits until the callee finishes processing.

Async : the caller receives an immediate acknowledgment and proceeds; the callee later notifies the result via callbacks or events.

Blocking vs Non‑blocking

Blocking : the calling thread is suspended until the operation completes.

Non‑blocking : the call returns immediately, allowing the thread to do other work.

Combining sync/async with blocking/non‑blocking yields terms such as BIO (blocking sync), NIO (non‑blocking sync), and AIO (non‑blocking async).

Java BIO

Traditional blocking IO (BIO) uses a ServerSocket to listen and a Socket for each client, typically creating one thread per connection. This model is simple but resource‑intensive for many connections.

Using a thread pool can mitigate thread‑creation overhead.

Java NIO

Introduced in JDK 1.4, NIO provides non‑blocking, synchronous IO using the Reactor pattern and selectors. Channels are registered with a selector; when a channel becomes readable or writable, the selector notifies the application.

Key NIO classes include SocketChannel , ServerSocketChannel , and FileChannel . Data is transferred via ByteBuffer objects.

Java AIO

Since JDK 7, Asynchronous IO (AIO) offers true async read/write methods that invoke callbacks upon completion. AIO is suitable for long‑lived connections with heavy IO.

2. NIO Core Components

1. Channel

All NIO IO operations start from a Channel , which reads/writes data using a Buffer . read() reads data into a buffer; write() writes buffer data to the channel.

Common channel implementations:

SocketChannel : client‑side TCP channel.

ServerSocketChannel : server‑side listening channel.

FileChannel : file read/write channel.

2. Buffer

Concept

A Buffer is a memory region used to hold data temporarily. The most common buffer is ByteBuffer , which wraps a byte[] array.

Properties

Important buffer attributes: capacity , limit , position , and mark . Their relationships are 0 <= mark <= position <= limit <= capacity .

Common Operations

allocate(int capacity) – creates a heap buffer.

wrap(byte[] array) – wraps an existing array.

allocateDirect(int capacity) – creates an off‑heap buffer.

put() / get() – write/read data.

flip() – switch from write mode to read mode.

clear() – reset buffer for writing.

rewind() – reset position to zero for rereading.

mark() and reset() – save and restore position.

3. Selector

Concept

A Selector (multiplexer) monitors multiple Channel objects for events such as OP_CONNECT, OP_ACCEPT, OP_READ, and OP_WRITE.

Polling Mechanism

Register channels with the selector (must be non‑blocking).

Call select() (or selectNow() , select(long timeout) ) to block until at least one channel is ready.

Retrieve ready keys via selector.selectedKeys() and handle the corresponding events.

Example Registration

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

3. Code Example

The following demonstrates a simple NIO server and client.

NIOServer.java

public class NIOServer {
    private static Selector selector;
    public static void main(String[] args) {
        init();
        listen();
    }
    private static void init() {
        ServerSocketChannel serverSocketChannel = null;
        try {
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(9000));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NioServer started");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void listen() {
        while (true) {
            try {
                selector.select();
                Iterator
keysIterator = selector.selectedKeys().iterator();
                while (keysIterator.hasNext()) {
                    SelectionKey key = keysIterator.next();
                    keysIterator.remove();
                    handleRequest(key);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }
    private static void handleRequest(SelectionKey key) throws IOException {
        SocketChannel channel = null;
        try {
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                channel = serverSocketChannel.accept();
                channel.configureBlocking(false);
                System.out.println("Accepted new Channel");
                channel.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {
                channel = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int count = channel.read(buffer);
                if (count > 0) {
                    System.out.println("Server received: " + new String(buffer.array(), 0, count));
                    channel.register(selector, SelectionKey.OP_WRITE);
                }
            }
            if (key.isWritable()) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.put("Received".getBytes());
                buffer.flip();
                channel = (SocketChannel) key.channel();
                channel.write(buffer);
                channel.register(selector, SelectionKey.OP_READ);
            }
        } catch (Throwable t) {
            t.printStackTrace();
            if (channel != null) {
                channel.close();
            }
        }
    }
}

NIOClient.java

public class NIOClient {
    public static void main(String[] args) {
        new Worker().start();
    }
    static class Worker extends Thread {
        @Override
        public void run() {
            SocketChannel channel = null;
            Selector selector = null;
            try {
                channel = SocketChannel.open();
                channel.configureBlocking(false);
                selector = Selector.open();
                channel.register(selector, SelectionKey.OP_CONNECT);
                channel.connect(new InetSocketAddress(9000));
                while (true) {
                    selector.select();
                    Iterator
keysIterator = selector.selectedKeys().iterator();
                    while (keysIterator.hasNext()) {
                        SelectionKey key = keysIterator.next();
                        keysIterator.remove();
                        if (key.isConnectable()) {
                            channel = (SocketChannel) key.channel();
                            if (channel.isConnectionPending()) {
                                channel.finishConnect();
                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                buffer.put("你好".getBytes());
                                buffer.flip();
                                channel.write(buffer);
                            }
                            channel.register(selector, SelectionKey.OP_READ);
                        }
                        if (key.isReadable()) {
                            channel = (SocketChannel) key.channel();
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            int len = channel.read(buffer);
                            if (len > 0) {
                                System.out.println("[" + Thread.currentThread().getName() + "] received: " + new String(buffer.array(), 0, len));
                                Thread.sleep(5000);
                                channel.register(selector, SelectionKey.OP_WRITE);
                            }
                        }
                        if (key.isWritable()) {
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            buffer.put("你好".getBytes());
                            buffer.flip();
                            channel = (SocketChannel) key.channel();
                            channel.write(buffer);
                            channel.register(selector, SelectionKey.OP_READ);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (channel != null) {
                    try { channel.close(); } catch (IOException e) { e.printStackTrace(); }
                }
                if (selector != null) {
                    try { selector.close(); } catch (IOException e) { e.printStackTrace(); }
                }
            }
        }
    }
}

4. Summary

Developing a NIO server involves creating a ServerSocketChannel , configuring it as non‑blocking, registering it with a Selector for OP_ACCEPT, looping on selector.select() , handling accept, read, and write events, and managing buffers and channel states appropriately.

Key takeaways include understanding the core NIO classes ( Selector , SocketChannel , ServerSocketChannel , FileChannel , ByteBuffer , SelectionKey ), the differences between BIO, NIO, and AIO, and the importance of handling network edge cases such as connection loss, back‑pressure, and packet framing.

JavaBackend DevelopmentNIONettynetwork programmingasynchronous io
Wukong Talks Architecture
Written by

Wukong Talks Architecture

Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.