Mastering Java I/O: From BIO to NIO and Epoll to Solve the c10k Challenge
This article walks through the evolution of Java I/O models—from blocking BIO to non‑blocking NIO and multiplexed epoll—explains core concepts such as blocking, non‑blocking, synchronous and asynchronous operations, and demonstrates practical code for handling thousands of concurrent connections efficiently.
The article explains the evolution of I/O models in Java, starting from basic probability concepts and introducing the industry‑wide c10k problem, then demonstrating the progression from BIO (Blocking I/O) to NIO (Non‑blocking I/O) and finally to multiplexing techniques.
IO Model Classification
BIO (Blocking I/O) : Synchronous blocking I/O where the thread waits for data to be read or written.
NIO (Non‑blocking I/O) : Synchronous non‑blocking I/O that uses an event‑driven model, allowing other work while waiting for I/O.
AIO (Asynchronous I/O) : Asynchronous non‑blocking I/O where the operation completes in the background and a callback notifies the application.
Understanding these concepts—blocking vs. non‑blocking and synchronous vs. asynchronous—is essential for optimizing performance and reliability of applications that heavily use I/O such as Redis, RocketMQ, Nacos, Dubbo, etc.
Illustrative Analogy
Traditional queue for ordering food represents BIO: you must wait for the previous person to finish before you can proceed.
Number‑ticket system for a hot‑pot restaurant represents NIO: you receive a ticket and can do other things while waiting for your turn.
Private dining room where food is delivered directly represents AIO: you place the order and the service brings the food without you needing to pick it up.
Concept Details
Blocking vs. Non‑blocking : In blocking, the thread is suspended until data is ready; in non‑blocking, the call returns immediately and the thread can continue other work.
Synchronous vs. Asynchronous : Synchronous I/O requires the application to read data after it becomes ready; asynchronous I/O lets the OS invoke a callback when data is ready.
Java Support Versions
Example
IO Model
Supported JDK Version
Queue line
BIO (Blocking I/O)
Before JDK 1.4
Number‑ticket line
NIO (Non‑blocking I/O)
JDK 1.4 and later (java.nio package)
Private dining room
AIO (Asynchronous I/O)
JDK 1.7 and later (AsynchronousSocketChannel)
Practical Demo – Solving the c10k Problem
The c10k problem asks how a server can handle 10,000 concurrent connections. The article provides Java code that first creates 30,000 client connections using BIO, then shows a BIO server implementation, followed by an NIO server, and finally a selector‑based multiplexing solution.
Client Simulation (BIO)
public class C10kTestClient {
static String ip = "192.168.160.74";
public static void main(String[] args) throws IOException {
LinkedList<SocketChannel> clients = new LinkedList<>();
InetSocketAddress serverAddr = new InetSocketAddress(ip, 9998);
IntStream.range(20000, 50000).forEach(i -> {
try {
SocketChannel client = SocketChannel.open();
client.bind(new InetSocketAddress(ip, i));
client.connect(serverAddr);
System.out.println("client:" + i + " connected");
clients.add(client);
} catch (IOException e) {
System.out.println("IOException" + i);
e.printStackTrace();
}
});
System.out.println("clients.size: " + clients.size());
// Block main thread
System.in.read();
}
}BIO Server
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(9998, 20);
System.out.println("server begin");
while (true) {
Socket client = server.accept(); // blocking
System.out.println("accept client" + client.getPort());
new Thread(() -> {
try {
InputStream in = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while (true) {
String data = reader.readLine(); // blocking
if (data != null) {
System.out.println(data);
} else {
client.close();
break;
}
}
System.out.println("client break");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}NIO Server
public class NIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
LinkedList<SocketChannel> clients = new LinkedList<>();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9998));
serverSocketChannel.configureBlocking(false); // non‑blocking
while (true) {
Thread.sleep(500);
SocketChannel client = serverSocketChannel.accept(); // returns null if no connection
if (client != null) {
client.configureBlocking(false);
int port = client.socket().getPort();
System.out.println("client..port: " + port);
clients.add(client);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
for (SocketChannel c : clients) {
int num = c.read(buffer);
if (num > 0) {
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(c.socket().getPort() + " : " + new String(data));
buffer.clear();
}
}
}
}
}Selector‑Based Multiplexing (epoll/select/poll)
public class SelectorNIOSimple {
private Selector selector = null;
int port = 9998;
public static void main(String[] args) {
SelectorNIOSimple service = new SelectorNIOSimple();
service.start();
}
public void initServer() {
try {
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(port));
selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
initServer();
while (true) {
try {
System.out.println("可处理事件数量 " + selector.keys().size());
while (selector.select() > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
acceptHandler(key);
} else if (key.isReadable()) {
readHandler(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void acceptHandler(SelectionKey key) {
try {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel client = ssc.accept();
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.register(selector, SelectionKey.OP_READ, buffer);
System.out.println("client connected:" + client.getRemoteAddress());
} catch (IOException e) {
e.printStackTrace();
}
}
public void readHandler(SelectionKey key) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
try {
while (true) {
int read = client.read(buffer);
if (read > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
client.write(buffer);
}
buffer.clear();
} else if (read == 0) {
break;
} else {
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}The article also discusses the underlying Linux mechanisms (select, poll, epoll), their limitations, and how Java’s Selector abstracts these mechanisms, allowing developers to choose the appropriate multiplexing strategy via JVM properties.
Conclusion
By understanding the differences between BIO, NIO, and AIO, and by leveraging multiplexing techniques such as epoll, developers can design servers that handle massive numbers of concurrent connections efficiently, reduce thread count, avoid file‑descriptor exhaustion, and achieve better performance and resource utilization.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
