Java網(wǎng)絡(luò)IO模型詳解(BIO、NIO、AIO)
簡(jiǎn)介
Java 支持三種網(wǎng)絡(luò) IO 模型:BIO、NIO、AIO。
- Java BIO 是同步阻塞模型,一個(gè)連接對(duì)應(yīng)一個(gè)線程,客戶端有連接請(qǐng)求時(shí)服務(wù)端就啟動(dòng)一個(gè)線程,即使這個(gè)連接不做任何事情也會(huì)占用線程資源。
- Java NIO 是同步非阻塞模型,一個(gè)線程可以處理多個(gè)連接,客戶端連接請(qǐng)求會(huì)注冊(cè)到多路復(fù)用器(Selector),多路復(fù)用器檢測(cè)到連接有 IO 時(shí)間就會(huì)處理。
- Java AIO 是異步非阻塞模型,AIO 引入了異步通道的概念,讀寫(xiě)異步通道會(huì)立刻返回,讀寫(xiě)的數(shù)據(jù)由 Future 或 CompletionHandler 進(jìn)一步處理。
BIO 適用于連接數(shù)少的場(chǎng)景,程序編寫(xiě)比較簡(jiǎn)單,對(duì)服務(wù)器的資源要求比較高,JDK1.4之前的唯一選擇。NIO 適用于連接數(shù)多的場(chǎng)景,例如聊天服務(wù)器、服務(wù)器間通訊等,程序編寫(xiě)比較復(fù)雜,JDK1.4開(kāi)始支持。AIO 也適用于連接數(shù)多的場(chǎng)景,但更加偏向于異步操作多的場(chǎng)景。
Java BIO
模型示例

客戶端代碼示例
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
public class BIOClient {
public static void main(String[] args) {
new BIOClient().start("localhost", 6666);
}
public void start(String host, int port) {
// 初始化 socket
Socket socket = new Socket();
try {
// 設(shè)置 socket 連接
SocketAddress remote = new InetSocketAddress(host, port);
socket.setSoTimeout(5000);
socket.connect(remote);
// 發(fā)送數(shù)據(jù)
PrintWriter writer = getWriter(socket);
writer.write("hello server");
writer.flush();
// // 發(fā)起請(qǐng)求
// PrintWriter writer = getWriter(socket);
// writer.write(compositeRequest(host));
// writer.flush();
//
// // 讀取響應(yīng)
// String msg;
// BufferedReader reader = getReader(socket);
// while ((msg = reader.readLine()) != null) {
// System.out.println(msg);
// }
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream in = socket.getInputStream();
return new BufferedReader(new InputStreamReader(in));
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream out = socket.getOutputStream();
return new PrintWriter(new OutputStreamWriter(out));
}
private String compositeRequest(String host) {
return "GET / HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: curl/7.43.0\r\n" +
"Accept: */*\r\n\r\n";
}
}服務(wù)端代碼示例
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServer {
public static void main(String[] args) throws Exception {
// 創(chuàng)建一個(gè)線程池
ExecutorService pool = Executors.newCachedThreadPool();
// 創(chuàng)建 ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
// 等待客戶端連接
final Socket socket = serverSocket.accept();
// 接收到一個(gè)客戶端連接 放入線程池進(jìn)行處理
pool.execute(() -> process(socket));
}
}
static void process(Socket socket) {
try {
byte[] bytes = new byte[1024];
// 通過(guò) socket 獲取輸入流
InputStream inputStream = socket.getInputStream();
// 循環(huán)讀取客戶端發(fā)送的數(shù)據(jù)
while (true) {
// 沒(méi)有數(shù)據(jù)的時(shí)候這里會(huì)阻塞等待
int read = inputStream.read(bytes);
if (read == -1) break;
// 輸出客戶端發(fā)送的數(shù)據(jù)
System.out.println(new String(bytes, 0, read));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}Java NIO
NIO 采用 Reactor 模式,屬于 IO 多路復(fù)用模型,可以用一個(gè)線程處理多個(gè)請(qǐng)求。NIO 有三大核心模塊,通道(Channel)、緩沖區(qū)(Buffer)、選擇器(Selector)。NIO 的非阻塞模式,使主線程在未發(fā)生數(shù)據(jù)讀寫(xiě)事件時(shí)無(wú)需阻塞,可以繼續(xù)做其他事情,這就大大增強(qiáng)了服務(wù)器的并發(fā)處理能力。
模型示例

Selector 對(duì)應(yīng)一個(gè)線程,一個(gè) Selector 可以對(duì)應(yīng)多個(gè) Channel,一個(gè) Channel 對(duì)應(yīng)一個(gè) Buffer。程序切換到哪個(gè) Channel 是由事件決定的,Selector 會(huì)根據(jù)不同的事件切換不同的 Channel。下圖描述了 Channel、Buffer 和 Selector 的關(guān)系。

MappedByteBuffer 簡(jiǎn)介
NIO 提供的 MappedByteBuffer 支持支持在內(nèi)存(堆外內(nèi)存)中修改文件,可以減少一次數(shù)據(jù)拷貝。文件同步的部分,由 NIO 自己完成。
代碼示例
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* 說(shuō)明 1.MappedByteBuffer 可讓文件直接在內(nèi)存(堆外內(nèi)存)修改,操作系統(tǒng)不需要拷貝一次
*/
public class MappedByteBufferTest {
public static void main(String[] args) throws Exception {
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
//獲取對(duì)應(yīng)的通道
FileChannel channel = randomAccessFile.getChannel();
/**
* 參數(shù) 1:FileChannel.MapMode.READ_WRITE 使用的讀寫(xiě)模式
* 參數(shù) 2:0:可以直接修改的起始位置
* 參數(shù) 3:5: 是映射到內(nèi)存的大小(不是索引位置),即將 1.txt 的多少個(gè)字節(jié)映射到內(nèi)存
* 可以直接修改的范圍就是 0-5
* 實(shí)際類(lèi)型 DirectByteBuffer
*/
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
mappedByteBuffer.put(0, (byte) 'H');
mappedByteBuffer.put(3, (byte) '9');
mappedByteBuffer.put(5, (byte) 'Y');//IndexOutOfBoundsException
randomAccessFile.close();
System.out.println("修改成功~~");
}
}
NIO 編程代碼原理分析圖
關(guān)于 NIO 非阻塞網(wǎng)絡(luò)編程相關(guān)的(Selector、SelectionKey、ServerScoketChannel 和 SocketChannel)關(guān)系梳理圖

服務(wù)端代碼示例
可以結(jié)合上面的原理圖觀察代碼實(shí)現(xiàn)細(xì)節(jié)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class GroupChatServer {
// 定義屬性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
// 構(gòu)造器執(zhí)行初始化工作
public GroupChatServer() {
try {
// 得到選擇器
selector = Selector.open();
// 監(jiān)聽(tīng)端口的主線程
listenChannel = ServerSocketChannel.open();
// 綁定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
// 設(shè)置非阻塞模式
listenChannel.configureBlocking(false);
// 將該 listenChannel 注冊(cè)到 selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
public void listen() {
try {
// 循環(huán)處理
while (true) {
int count = selector.select();
// 有事件處理
if (count > 0) {
// 遍歷得到 selectionKey 集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
// 取出 selectionKey
SelectionKey key = iterator.next();
// 監(jiān)聽(tīng)到 accept
if (key.isAcceptable()) {
SocketChannel sc = listenChannel.accept();
sc.configureBlocking(false);
// 將該 sc 注冊(cè)到 selector
sc.register(selector, SelectionKey.OP_READ);
// 提示
System.out.println(sc.getRemoteAddress() + " 上線 ");
}
if (key.isReadable()) {// 通道發(fā)送read事件,即通道是可讀的狀態(tài)
// 處理讀(專(zhuān)門(mén)寫(xiě)方法..)
readData(key);
}
// 當(dāng)前的 key 刪除,防止重復(fù)處理
iterator.remove();
}
} else {
System.out.println("等待....");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 發(fā)生異常處理....
}
}
// 讀取客戶端消息
public void readData(SelectionKey key) {
SocketChannel channel = null;
try {
// 得到 channel
channel = (SocketChannel) key.channel();
// 創(chuàng)建 buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);// NIO這里不會(huì)阻塞 因?yàn)槭录|發(fā)時(shí)必然已經(jīng)有數(shù)據(jù)了 所以叫非阻塞IO
// 根據(jù) count 的值做處理
if (count > 0) {
// 把緩存區(qū)的數(shù)據(jù)轉(zhuǎn)成字符串
String msg = new String(buffer.array());
// 輸出該消息
System.out.println("form客戶端:" + msg);
// 向其它的客戶端轉(zhuǎn)發(fā)消息(去掉自己),專(zhuān)門(mén)寫(xiě)一個(gè)方法來(lái)處理
sendInfoToOtherClients(msg, channel);
}
} catch (IOException e) {
try {
System.out.println(channel.getRemoteAddress() + "離線了..");
// 取消注冊(cè)
key.cancel();
// 關(guān)閉通道
channel.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
// 轉(zhuǎn)發(fā)消息給其它客戶(通道)
private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {
System.out.println("服務(wù)器轉(zhuǎn)發(fā)消息中...");
// 遍歷所有注冊(cè)到 selector 上的 SocketChannel,并排除 self
for (SelectionKey key : selector.keys()) {
// 通過(guò) key 取出對(duì)應(yīng)的 SocketChannel
Channel targetChannel = key.channel();
// 排除自己
if (targetChannel instanceof SocketChannel && targetChannel != self) {
// 轉(zhuǎn)型
SocketChannel dest = (SocketChannel) targetChannel;
// 將 msg 存儲(chǔ)到 buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
// 將 buffer 的數(shù)據(jù)寫(xiě)入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
// 創(chuàng)建服務(wù)器對(duì)象
GroupChatServer groupChatServer = new GroupChatServer();
groupChatServer.listen();
}
}Java AIO
AIO 是異步非阻塞的,引入了異步通道的概念,采用 Proactor 模式,操作系統(tǒng)完成數(shù)據(jù)拷貝操作后才會(huì)通知服務(wù)端線程。AIO 本質(zhì)上還是 IO 多路復(fù)用模型,與 NIO 比起來(lái),AIO 只是在非阻塞的前提下增加了異步功能,具體則體現(xiàn)在代碼編寫(xiě)以及數(shù)據(jù)傳輸兩個(gè)層面。
- 從代碼編寫(xiě)角度來(lái)說(shuō),原來(lái)的同步方法會(huì)阻塞等待接口返回,而現(xiàn)在可以異步等待返回結(jié)果。
- 從數(shù)據(jù)傳輸角度來(lái)說(shuō),每個(gè)請(qǐng)求都需要傳輸數(shù)據(jù),NIO 雖然是非阻塞的,但是事件到達(dá)后,NIO 需要自己把數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間。AIO 引入異步邏輯后,事件到達(dá)后系統(tǒng)不會(huì)立刻通知服務(wù)端線程,而是會(huì)自己把數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間,完成這個(gè)操作后,才會(huì)通知服務(wù)端線程去處理。
AIO 的使用場(chǎng)景還是比較少,現(xiàn)在大部分開(kāi)源框架中應(yīng)該還是以使用 NIO 為主,AIO 在性能方面的提升還是比較有限,主要的變化還是增加了異步功能。
如何理解 Reactor 和 Proactor 的區(qū)別?
Reactor 可以理解為「來(lái)了事件操作系統(tǒng)通知應(yīng)用進(jìn)程,讓?xiě)?yīng)用進(jìn)程來(lái)處理」,而Proactor 可以理解為「來(lái)了事件操作系統(tǒng)來(lái)處理,處理完再通知應(yīng)用進(jìn)程」。
總結(jié)
到此這篇關(guān)于Java網(wǎng)絡(luò)IO模型(BIO、NIO、AIO)的文章就介紹到這了,更多相關(guān)Java網(wǎng)絡(luò)BIO、NIO、AIO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java中布隆過(guò)濾器(Bloom Filter)原理及其使用場(chǎng)景
布隆過(guò)濾器是1970年由布隆提出的,它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù),它的作用是檢索一個(gè)元素是否存在我們的集合之中,本文給大家詳細(xì)的講解一下布隆過(guò)濾器,感興趣的同學(xué)可以參考閱讀2023-05-05
spring中實(shí)現(xiàn)容器加載完成后再執(zhí)行自己的方法
這篇文章主要介紹了spring中實(shí)現(xiàn)容器加載完成后再執(zhí)行自己的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
如何實(shí)現(xiàn)自己的spring boot starter
這篇文章主要介紹了如何實(shí)現(xiàn)自己的spring boot starter,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Spring框架學(xué)習(xí)之Spring?@Autowired實(shí)現(xiàn)自動(dòng)裝配的代碼
自動(dòng)裝配就是說(shuō),你不用手動(dòng)實(shí)現(xiàn)bean之間的組合關(guān)系,只要使用了@Autowired注解,程序就會(huì)自動(dòng)的注入這個(gè)需要的bean,前提是你的Spring容器有這個(gè)bean,這篇文章主要介紹了Spring?@Autowired實(shí)現(xiàn)自動(dòng)裝配,需要的朋友可以參考下2021-12-12
Java單例模式利用HashMap實(shí)現(xiàn)緩存數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Java單例模式利用HashMap實(shí)現(xiàn)緩存數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
idea導(dǎo)入項(xiàng)目不顯示maven側(cè)邊欄的問(wèn)題及解決方法
這篇文章主要介紹了idea導(dǎo)入項(xiàng)目不顯示maven側(cè)邊欄的問(wèn)題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
java通過(guò)jni調(diào)用opencv處理圖像的方法
今天小編就為大家分享一篇java通過(guò)jni調(diào)用opencv處理圖像的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08

