Java?BOI與NIO超詳細實例精講
Java BIO
阻塞IO,每個客戶端鏈接都需要一個獨立的線程處理,客戶端鏈接沒關(guān)閉時,線程鏈接處于阻塞狀態(tài),直到客戶端鏈接關(guān)閉
如果客戶端鏈接沒有讀取到數(shù)據(jù),鏈接就會一直阻塞住,造成資源浪費

示例代碼
開發(fā)一個ServerSocket服務(wù)端,通過telnet鏈接發(fā)送信息
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServerTest {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服務(wù)端啟動");
while (true) {
System.out.println("等待鏈接...");
Socket socket = serverSocket.accept();
pool.execute(() -> {
handler(socket);
});
}
}
private static void handler(Socket socket) {
try {
System.out.println("有一個客戶端接入:" + Thread.currentThread().getName());
byte[] bytes = new byte[1024];
//通過socket 獲取輸入流
InputStream inputStream = socket.getInputStream();
//循環(huán)讀取客戶端發(fā)送的數(shù)據(jù)
while (true) {
System.out.println("等待讀取數(shù)據(jù)...");
int read = inputStream.read(bytes);
if (read != -1) {
//輸出客戶端讀取的數(shù)據(jù)
System.out.println("線程信息:" + Thread.currentThread().getName());
System.out.println(new String(bytes, 0, read));
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("關(guān)閉client鏈接:" + Thread.currentThread().getName());
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}通過telnet鏈接兩個客戶端,分別發(fā)送請求
從控制臺打印信息可以看出每一個鏈接對應(yīng)的線程都是獨立的
等待鏈接...
有一個客戶端接入:pool-1-thread-1
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-1
aaa
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-1
bbb
等待讀取數(shù)據(jù)...
等待鏈接...
有一個客戶端接入:pool-1-thread-2
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-2
ccc
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-2
ddd
等待讀取數(shù)據(jù)...
Java NIO
非阻塞IO,通過Selector和Channel,實現(xiàn)一個線程維護多個鏈接
NIO有三大核心部分:Channel(通道),Buffer(緩沖區(qū)),Selector(選擇器)
NOI是面向緩沖區(qū)編程的,數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動,增加的處理過程的靈活性,使用它可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)
NIO是通過事件驅(qū)動編程的,Selector會根據(jù)不同的事件,在各個通道上切換
Channel同時支持讀寫雙向處理
Selector能夠監(jiān)測到多個注冊的通到是否有事件發(fā)生,這樣就可以只用一個線程去管理多個通道,也就是管理多個鏈接和請求。
只有在鏈接真正有讀寫事件發(fā)生時,才會進行讀寫,大大減少了系統(tǒng)開銷,并且不必為每個鏈接創(chuàng)建一個線程

代碼解讀
- 當(dāng)客戶端鏈接時,會通過ServerSocketChannel得到SocketChannel
- 將SocketChannel注冊到Selector上,一個selector上可以注冊多個SocketChannel
- 通過socketChannel.register()方法注冊
- 注冊后返回一個SelectionKey,會和該Selector關(guān)聯(lián)
- Selector監(jiān)聽select方法,返回有事件發(fā)生的通道的個數(shù)
- 進一步得到有事件發(fā)生的各個SelectionKey,通過SelectionKey反向獲取SocketChannel
- 通過得到的channel完成業(yè)務(wù)處理
1)服務(wù)端代碼
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NIOServer {
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
serverSocketChannel.socket().bind(inetSocketAddress);
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
//注冊客戶端鏈接事件到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int select = selector.select(1000L);
if (select == 0) {
System.out.println("等待1秒,沒有事件發(fā)生");
continue;
}
//監(jiān)聽到相關(guān)事件發(fā)生,讀取SelectionKey集合,遍歷處理所有事件
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客戶端鏈接成功,生成一個sockeChannel " + socketChannel.hashCode());
//將socketChannel設(shè)置為非阻塞
socketChannel.configureBlocking(false);
//注冊內(nèi)容讀取事件到selector,同時關(guān)聯(lián)一個Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {
SocketChannel channel = null;
try {
channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
int read = channel.read(byteBuffer);
System.out.println("讀取到數(shù)據(jù): " + new String(byteBuffer.array(), 0, read));
} catch (Exception e) {
if (channel != null) {
channel.close();
}
e.printStackTrace();
}
}
//手動從集合中移除當(dāng)前的SelectionKey,防止重復(fù)操作
keyIterator.remove();
}
}
}
}2)客戶端代碼
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 7000);
if (!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("鏈接未完成,客戶端不會阻塞....");
}
}
String str = "Hello,你好~~~";
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
socketChannel.write(byteBuffer);
System.in.read();
}
}
控制臺輸出
等待1秒,沒有事件發(fā)生
等待1秒,沒有事件發(fā)生
等待1秒,沒有事件發(fā)生
等待1秒,沒有事件發(fā)生
等待1秒,沒有事件發(fā)生
客戶端鏈接成功,生成一個sockeChannel 60559178
讀取到數(shù)據(jù): Hello,你好~~~
等待1秒,沒有事件發(fā)生
到此這篇關(guān)于Java BOI與NIO超詳細實例精講的文章就介紹到這了,更多相關(guān)Java BOI與NIO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器
這篇文章主要介紹了使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
SpringCloud之監(jiān)控數(shù)據(jù)聚合Turbine的實現(xiàn)
這篇文章主要介紹了SpringCloud之監(jiān)控數(shù)據(jù)聚合Turbine的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Java8函數(shù)式接口的基礎(chǔ)學(xué)習(xí)教程
這篇文章主要給大家介紹了關(guān)于Java8函數(shù)式接口基礎(chǔ)學(xué)習(xí)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
一篇文章帶你解決 IDEA 每次新建項目 maven home directory 總是改變的問題
這篇文章主要介紹了一篇文章帶你解決 IDEA 每次新建項目 maven home directory 總是改變的問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
使用Jenkins一鍵打包部署SpringBoot項目的步驟詳解
任何簡單操作的背后,都有一套相當(dāng)復(fù)雜的機制,本文將以SpringBoot應(yīng)用的在Docker環(huán)境下的打包部署為例,詳細講解如何使用Jenkins一鍵打包部署SpringBoot應(yīng)用,文中通過圖文結(jié)合講解的非常詳細,需要的朋友可以參考下2023-11-11

