一文帶你搞懂java如何實現(xiàn)網(wǎng)絡(luò)NIO高并發(fā)編程
Java NIO 簡介及高并發(fā)網(wǎng)絡(luò)編程實現(xiàn)
Java NIO
NIO(Non-blocking I/O,非阻塞 I/O)是 Java 在 JDK 1.4 中引入的一套新的 I/O API,旨在解決傳統(tǒng) I/O(即 BIO,阻塞 I/O)在高并發(fā)場景下的性能和擴展性不足的問題。
NIO 的核心特點
非阻塞 I/O:支持非阻塞模式,可以讓線程不必一直等待 I/O 操作完成,從而提高系統(tǒng)資源利用率。
基于緩沖區(qū)(Buffer):數(shù)據(jù)的讀寫通過緩沖區(qū)進行,而不是直接通過流(Stream)。
選擇器(Selector):通過一個線程管理多個通道(Channel),極大地提升了高并發(fā)場景下的擴展性和效率。
多路復(fù)用:通過 Selector 機制可以同時監(jiān)控多個通道的狀態(tài)(如連接就緒、讀數(shù)據(jù)就緒等)。
BIO(阻塞 I/O)與 NIO(非阻塞 I/O) 對比
| 特性 | BIO | NIO |
|---|---|---|
| I/O 模式 | 阻塞,線程會等待 I/O 完成 | 非阻塞,線程無需等待 I/O 完成 |
| 線程模型 | 每個連接一個線程 | 一個線程管理多個連接 |
| 適用場景 | 低并發(fā)、簡單場景 | 高并發(fā)、網(wǎng)絡(luò)編程場景 |
| 性能 | 線程資源成本高,擴展性差 | 更高效的資源利用,擴展性更好 |
NIO 的核心組件
1.Channel(通道)
類似流(Stream),但 Channel 同時支持讀和寫。
常見的 Channel:SocketChannel、ServerSocketChannel、DatagramChannel、FileChannel。
2.Buffer(緩沖區(qū))
數(shù)據(jù)讀寫通過 Buffer 進行。
常見的緩沖區(qū):ByteBuffer、CharBuffer、IntBuffer 等。
3.Selector(選擇器)
核心組件,用于監(jiān)聽多個通道的事件,如連接就緒、讀就緒、寫就緒等。
通過多路復(fù)用機制實現(xiàn)一個線程管理多個通道。
4.SelectionKey
表示通道和選擇器的注冊關(guān)系,包含通道的事件類型(如讀、寫、連接等)。
Java NIO 網(wǎng)絡(luò)高并發(fā)編程示例
場景描述
服務(wù)器端:監(jiān)聽客戶端請求,接收數(shù)據(jù)并返回信息。
客戶端:連接服務(wù)器,發(fā)送數(shù)據(jù)并接收響應(yīng)。
服務(wù)器端代碼
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioServer {
public static void main(String[] args) {
try {
// 1. 創(chuàng)建 ServerSocketChannel 用于監(jiān)聽客戶端連接
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false); // 設(shè)置為非阻塞模式
// 2. 創(chuàng)建 Selector 并注冊 ServerSocketChannel,監(jiān)聽 ACCEPT 事件
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Server started on port 8080...");
while (true) {
// 3. 檢查是否有事件發(fā)生,阻塞等待
if (selector.select() == 0) {
continue; // 如果沒有事件就緒,繼續(xù)循環(huán)
}
// 4. 獲取就緒的事件集合
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove(); // 移除當前處理的 key,避免重復(fù)處理
try {
// 5. 處理不同的事件
if (key.isAcceptable()) { // 客戶端連接事件
handleAccept(key, selector);
} else if (key.isReadable()) { // 讀取客戶端數(shù)據(jù)事件
handleRead(key);
}
} catch (IOException e) {
System.err.println("Error handling client connection: " + e.getMessage());
key.cancel(); // 取消出錯的鍵
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 處理 ACCEPT 事件
private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept(); // 接受客戶端連接
clientChannel.configureBlocking(false); // 設(shè)置為非阻塞模式
clientChannel.register(selector, SelectionKey.OP_READ); // 注冊 READ 事件
System.out.println("Client connected: " + clientChannel.getRemoteAddress());
}
// 處理 READ 事件
private static void handleRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead;
try {
bytesRead = clientChannel.read(buffer); // 從通道中讀取數(shù)據(jù)
} catch (SocketException e) {
System.err.println("Connection reset by client: " + clientChannel.getRemoteAddress());
clientChannel.close();
key.cancel();
return;
}
if (bytesRead > 0) {
buffer.flip(); // 切換到讀取模式
String message = new String(buffer.array(), 0, buffer.limit());
System.out.println("Received from client: " + message);
// 回寫數(shù)據(jù)到客戶端
buffer.clear();
buffer.put(("Echo: " + message).getBytes());
buffer.flip();
clientChannel.write(buffer);
} else if (bytesRead == -1) { // 客戶端斷開連接
System.out.println("Client disconnected: " + clientChannel.getRemoteAddress());
clientChannel.close();
key.cancel(); // 取消注冊的事件
}
}
}
客戶端代碼
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClient {
public static void main(String[] args) throws IOException {
// 1. 創(chuàng)建 SocketChannel 連接服務(wù)器
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080))) {
// 等待連接完成
while (!socketChannel.finishConnect()) {
System.out.println("Connecting to server...");
}
}
System.out.println("Connected to the server");
// 2. 發(fā)送數(shù)據(jù)到服務(wù)器
String message = "Hello, NIO Server!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
socketChannel.write(buffer);
// 3. 接收服務(wù)器回寫的數(shù)據(jù)
buffer.clear();
int bytesRead = socketChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
String response = new String(buffer.array(), 0, buffer.limit());
System.out.println("Received from server: " + response);
}
socketChannel.close();
}
}
運行結(jié)果
客戶端輸出
Connected to the server
Received from server: Echo: Hello, NIO S
進程已結(jié)束,退出代碼為 0
服務(wù)器端輸出
NIO Server started on port 8080...
Client connected: /127.0.0.1:50104
Received from client: Hello, NIO Server!
Connection reset by client: /127.0.0.1:50104
NIO 高并發(fā)的關(guān)鍵點
非阻塞 I/O:通過 Selector 一個線程可以同時監(jiān)聽多個客戶端連接,無需為每個連接創(chuàng)建一個線程,降低了線程開銷。
多路復(fù)用:Selector 提供多路復(fù)用機制,能同時監(jiān)聽多個事件(如連接就緒、讀就緒等)。
IO 操作優(yōu)化:通過 ByteBuffer 進行 I/O 操作,避免了傳統(tǒng)流的頻繁數(shù)據(jù)拷貝,提高了讀寫效率。
NIO 的局限性和改進
1.局限性
NIO 的使用相對復(fù)雜,需要手動管理通道和緩沖區(qū)。
在高并發(fā)場景下,Selector 的性能可能成為瓶頸。
2.改進方向
Netty:一個基于 NIO 的高性能網(wǎng)絡(luò)框架,簡化了 NIO 的使用,同時提供了更高的吞吐量和擴展性。
異步 I/O(AIO):Java NIO 2.0(JDK 7 引入)提供了異步 I/O,進一步優(yōu)化了線程資源利用。
適用場景和建議
適用場景:高并發(fā)的網(wǎng)絡(luò)應(yīng)用,例如 Web 服務(wù)器、消息推送服務(wù);I/O 密集型應(yīng)用。
使用建議:對于復(fù)雜的高性能網(wǎng)絡(luò)應(yīng)用,建議使用 Netty 等成熟框架,避免直接操作 NIO 的底層代碼。
以上就是一文帶你搞懂java如何實現(xiàn)網(wǎng)絡(luò)NIO高并發(fā)編程的詳細內(nèi)容,更多關(guān)于java網(wǎng)絡(luò)NIO高并發(fā)編程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring實戰(zhàn)之使用c:命名空間簡化配置操作示例
這篇文章主要介紹了Spring實戰(zhàn)之使用c:命名空間簡化配置操作,結(jié)合實例形式詳細分析了Spring使用c:命名空間簡化配置的相關(guān)接口與配置操作技巧,需要的朋友可以參考下2019-12-12
Java使用POI-TL和JFreeChart動態(tài)生成Word報告
本文介紹了使用POI-TL和JFreeChart生成包含動態(tài)數(shù)據(jù)和圖表的Word報告的方法,并分享了實際開發(fā)中的踩坑經(jīng)驗,通過代碼示例講解的非常詳細,具有一定的參考價值,需要的朋友可以參考下2025-02-02
在Struts2中如何將父類屬性序列化為JSON格式的解決方法
本篇文章,小編將為大家介紹關(guān)于在Struts2中如何將父類屬性序列化為JSON格式的解決方法,有需要的朋友可以參考一下2013-04-04
Java多線程的調(diào)度_動力節(jié)點Java學(xué)院整理
有多個線程,如何控制它們執(zhí)行的先后次序呢?下文給大家分享四種方法及java多線程調(diào)度的實例代碼,需要的朋友參考下吧2017-05-05

