Netty學習之理解selector原理示例
BIO的弊端
BIO既是Blocking IO,也叫同步阻塞模型,BIO模型如下

如果所示,多個客戶端連接一個服務端, 每出現(xiàn)一個客戶端就開一個handler(一般對應一個線程)處理
對應的服務端代碼如下
public class BioServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9000);
// 任務處理線程池
ExecutorService pool = Executors.newFixedThreadPool(10);
while (true) {
System.out.println("server start");
//阻塞方法等待客戶端連接
Socket clientSocket = serverSocket.accept();
System.out.println("new client");
pool.execute(()->{
handler(clientSocket);
});
}
}
private static void handler(Socket clientSocket) {
try {
byte[] bytes = new byte[1024];
System.out.println("reading");
//接收客戶端的數(shù)據(jù),阻塞方法,沒有數(shù)據(jù)可讀時就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read over");
if (read != -1) {
System.out.println("received data:" + new String(bytes, 0, read));
}
clientSocket.getOutputStream().write("HelloClient".getBytes());
clientSocket.getOutputStream().flush();
} catch (Exception e) {
}
}
}上面的代碼使用了線程池來處理客戶端業(yè)務,整個代碼有兩步阻塞
- 一步主線程阻塞等待客戶端連接
- 一步線程池的線程阻塞等待客戶端傳輸數(shù)據(jù)準備好
其實第一步阻塞到?jīng)]什么,作為一個服務沒有客戶的情況下不阻塞也無事可做,主要是第二個阻塞,上面代碼線程池size是10,假如當前有10個客戶端連接都不發(fā)送數(shù)據(jù),那么10個線程都阻塞,此時再來一個客戶端發(fā)送完數(shù)據(jù)也無法及時處理
假設現(xiàn)在有個澡堂子,一共雇傭10個搓澡工,BIO就好比來一個客人就分配一個搓澡師傅,這個師傅就死死盯著客人洗澡,啥時候洗澡完了就開始安排搓澡,如果十個客戶在池子里洗澡洗不完,這時有11號客戶來了,沒有空閑的搓澡師傅(都在等待客戶洗完澡),那這個客戶只能傻傻的等著,實際上十個搓澡工都沒有實際干活,但卻處于傻傻的等待不可用狀態(tài)
解決思路
通過上面的例子可以看出,BIO的模型明顯是反人類的,合理的工作流程是這樣
10個搓澡師傅都待著,什么時候有人洗完澡了才分配搓澡師傅進行搓澡,這樣搓澡師傅的工作更加合理,充分的壓榨了搓澡師傅
客戶只會因為搓澡師傅都在搓澡而等待,而不是因為搓澡師傅都在傻等而等待
這樣設計顯然更加合理,也更加符合現(xiàn)實的工作流程,相當于事件驅動,當發(fā)生洗完澡(接收到數(shù)據(jù))事件后,再安排搓澡工(線程)進行處理
回到代碼怎么才能做到以事件驅動吶?更確切的說,作為java代碼,如何得知數(shù)據(jù)傳輸?shù)氖录l(fā)生吶?
顯然java本身肯定是做不到的,我們可以想一下數(shù)據(jù)的來源,一個網(wǎng)絡數(shù)據(jù)的傳輸,一臺機器通過網(wǎng)線或無線等形式發(fā)送二進制數(shù)據(jù)到一臺電腦,硬件的驅動必然能感知到,那么操作系統(tǒng)也一定能感知到,所以一個鏈接是否有數(shù)據(jù)傳輸,操作系統(tǒng)最知道!
epoll
linux 提供了epoll系列函數(shù)

上層代碼可以通過調用該系列函數(shù)訂閱感興趣的事件,后續(xù)即可感知到注冊事件的發(fā)生,主要方法如下
- epoll_create: 創(chuàng)建一個epoll實例
- epoll_ctl: 訂閱事件
- epoll_wait: 阻塞等待訂閱事件的發(fā)生
有個這系列函數(shù),java代碼就可以:
- 調用epoll_create創(chuàng)建一個epoll實例
- 調用epoll_ctl訂閱可讀事件(相當于有數(shù)據(jù)傳輸事件)
- 調用epoll_wait阻塞并等待時間發(fā)生
當然不能直接調用,而是調用底層C++再調用linux函數(shù)
NIO
有了操作系統(tǒng)的支持,java就可以實現(xiàn)一個不阻塞的IO服務,這就是NIO模型(Non Blocking IO)
JDK1.4開始引入java.nio包,在linux系統(tǒng)中底層就是使用epoll實現(xiàn)的(windows中基于winsock2,不開源)
先看一下NIO實現(xiàn)服務的代碼
public class NioServer {
public static void main(String[] args) throws IOException, InterruptedException {
// 創(chuàng)建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
serverSocket.configureBlocking(false);
// 打開Selector處理Channel,底層調用epoll_create
Selector selector = Selector.open();
// 把ServerSocketChannel注冊到selector上,并且selector對客戶端accept連接操作感興趣,底層調用epoll_ctl
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服務啟動成功");
while (true) {
// 阻塞等待需要處理的事件發(fā)生,即調用epoll_wait
selector.select();
// 獲取selector中注冊的全部事件的 SelectionKey 實例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍歷SelectionKey對事件進行處理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件,則進行連接獲取和事件注冊
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 這里只注冊了讀事件,如果需要給客戶端發(fā)送數(shù)據(jù)可以注冊寫事件,底層調用epoll_ctl
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客戶端連接成功");
} else if (key.isReadable()) { // 如果是OP_READ事件,則進行讀取和打印
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// 如果有數(shù)據(jù),把數(shù)據(jù)打印出來
if (len > 0) {
System.out.println("接收到消息:" + new String(byteBuffer.array()));
} else if (len == -1) { // 如果客戶端斷開連接,關閉Socket
System.out.println("客戶端斷開連接");
socketChannel.close();
}
}
//從事件集合里刪除本次處理的key,防止下次select重復處理
iterator.remove();
}
}
}
}上面及是一個典型的NIO代碼,其中(liunx中):
- Selector.open() 底層調用liunx的
epoll_create - socketChannel.register(selector, SelectionKey.OP_READ)相當于調用liunx的
epoll_ctl(實際上只是緩存起來,下一步再真正執(zhí)行epoll_ctl) - selector.select() 底層調用
epoll_wait,阻塞并等待訂閱事件發(fā)生
總結
selector 是java.nio包中一個nio解決方案,主要可以實現(xiàn)
- 訂閱IO事件,如連接事件和有數(shù)據(jù)傳輸事件
- 阻塞并等待任何訂閱事件的發(fā)生
在linux內(nèi)核中,selector就是對epoll函數(shù)的一種封裝,或者說epoll是linux針對java selector的實現(xiàn)
以上就是Netty學習之理解selector原理示例的詳細內(nèi)容,更多關于Netty selector原理的資料請關注腳本之家其它相關文章!
相關文章
Java swing讀取txt文件實現(xiàn)學生考試系統(tǒng)
這篇文章主要為大家詳細介紹了Java swing讀取txt文件實現(xiàn)學生考試系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-06-06
Spring工程中集成多個redis數(shù)據(jù)源的思路詳解
Spring項目集成雙Redis數(shù)據(jù)源需配置兩個連接工廠與RedisTemplate,通過@Primary指定默認Bean,@Qualifier注入特定Bean,支持集群環(huán)境,本文給大家介紹Spring工程中集成多個redis數(shù)據(jù)源的相關知識,感興趣的朋友一起看看吧2025-07-07
IDEA2020.2.3中創(chuàng)建JavaWeb工程的完整步驟記錄
這篇文章主要給大家介紹了關于IDEA2020.2.3中創(chuàng)建JavaWeb工程的完整步驟,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
springboot?集成easy-captcha實現(xiàn)圖像驗證碼顯示和登錄
本文主要介紹了springboot?集成easy-captcha實現(xiàn)圖像驗證碼顯示和登錄,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04

