Netty與NIO超詳細講解
Linux下的五種I/O模型
1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O復(fù)用(select 和poll) (I/O multiplexing)
4)信號驅(qū)動I/O (signal driven I/O (SIGIO))
5)異步I/O (asynchronous I/O (the POSIX aio_functions))
前面四種都是同步io、第五種是異步IO;
阻塞式:當(dāng)程序沒有獲取到數(shù)據(jù)的時候,整個應(yīng)用可能會產(chǎn)生阻塞,放棄了CPU執(zhí)行,無法去做其他事情。
非阻塞式:不管有沒有獲取到數(shù)據(jù),都必須立馬返回一個結(jié)果,如果沒有獲取到數(shù)據(jù)的情況下返回一個錯誤標(biāo)記,根據(jù)錯誤的標(biāo)記不斷輪詢。BIO屬于阻塞式的io操作??梢允褂枚嗑€程實現(xiàn)異步I0,同時處理多個請求。缺點:非常消耗服務(wù)器資源CPU
阻塞IO的流程
BIO屬于阻塞式的io操作。
可以使用多線程實現(xiàn)異步IO,同時處理多個請求。缺點:非常消耗服務(wù)器資源CPU
當(dāng)我們在調(diào)用一個io函數(shù)的時候,如果沒有獲取到數(shù)據(jù)的情況下,那么就會一直等待;等待的過程中會導(dǎo)致整個應(yīng)用程序一直是一個阻塞的過程,無法去做其他的實現(xiàn)。
IO復(fù)用
IO復(fù)用機制:IO實際指的就是網(wǎng)絡(luò)的IO、多路也就是多個不同的tcp連接;復(fù)用也就是指使用同一個線程合并處理多個不同的IO操作,這樣的話可以減少CPU資源。
信號驅(qū)動I/O
發(fā)出一個請求實現(xiàn)觀察監(jiān)聽,當(dāng)有數(shù)據(jù)的時候直接走我們異步回調(diào);
異步IO
異步io也就是發(fā)出請求數(shù)據(jù)之后,剩下的事情完全實現(xiàn)異步完成
同步與異步
站在多線程的角度總結(jié):
同步整個應(yīng)用代碼執(zhí)行順序是從上往下執(zhí)行 并且返回到結(jié)果;
異步:開啟多個不同的分支實現(xiàn)并行執(zhí)行 每個線程互不影響;
站在web項目角度分析
默認的情況Http請求就是一個同步形式實現(xiàn)調(diào)用 基于請求與響應(yīng),如果我們響應(yīng)非常耗時的話,會導(dǎo)致客戶端一直等待(用戶體驗非常不好)
NIO
Java的nio是在Jdk1.4版本之后推出了一套新的io方案,這種io方案對原有io做了一次性能上的升級
NIO翻譯成英文 no blocking io 簡稱為 nio 非阻塞io,不是new io。
比傳統(tǒng)的io支持了面向緩沖區(qū)、基于通道實現(xiàn)的io的方案。
BIO 與 NIO 區(qū)別:
Bio是一個阻塞式的io,它是西向與流傳輸也就是根據(jù)每個字節(jié)實現(xiàn)傳輸效率非常低,
而我們的nio是雨向與緩沖區(qū)的非阻塞邊.其中最大的亮點:運多路復(fù)用機制,
I0多路復(fù)用
多路實際上指的是多個不同的 tcp 連接。
復(fù)用:一個線程可以維護多個不同的io操作。
優(yōu)點:占用cpu資源非常小、保證線程安全問題。
IO 多路復(fù)用實現(xiàn)原理
使用一個Java案例來描述IO多路復(fù)用的思路:
public class SocketNioTcpServer { private static List<SocketChannel> listSocketChannel = new ArrayList<>(); private static ByteBuffer byteBuffer = ByteBuffer.allocate(512); public static void main(String[] args) { try { // 1.創(chuàng)建一個ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2. 綁定地址 ServerSocketChannel bind = serverSocketChannel.bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); while (true) { SocketChannel socketChannel = serverSocketChannel.accept(); if (socketChannel != null) { socketChannel.configureBlocking(false); listSocketChannel.add(socketChannel); } for (SocketChannel scl : listSocketChannel) { try { int read = scl.read(byteBuffer); if (read > 0) { byteBuffer.flip(); Charset charset = Charset.forName("UTF-8"); String receiveText = charset.newDecoder().decode (byteBuffer.asReadOnlyBuffer()).toString(); System.out.println("receiveText:" + receiveText); } } catch (Exception e) { e.printStackTrace(); } } } } catch (Exception e) { e.printStackTrace(); } } }
NIO核心組件
通道(Channel)
通常我們nio所有的操作都是通過通道開始的,所有的通道都會注冊到統(tǒng)一個選擇器(Selector)上實現(xiàn)管理,在通過選擇器將數(shù)據(jù)統(tǒng)一寫入到 buffer中。
緩沖區(qū)(Buffer)
Buffer本質(zhì)上就是一塊內(nèi)存區(qū),可以用來讀取數(shù)據(jù),也就先將數(shù)據(jù)寫入到緩沖區(qū)中、在統(tǒng)一的寫入到硬盤上。
選擇器(Selector)
Selector可以稱做為選擇器,也可以把它叫做多路復(fù)用器,可以在單線程的情況下可以去維護多個Channel,也可以去維護多個連接;
使用Java原生API實現(xiàn)NIO操作
public class NIOServer { /** * 創(chuàng)建一個選擇器 */ private Selector selector; public void initServer(int port) throws IOException { // 獲得一個ServerSocketChannel通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 設(shè)置通道為非阻塞 serverSocketChannel.configureBlocking(false); // 將該通道對應(yīng)的ServerSocket綁定到port端口 serverSocketChannel.bind(new InetSocketAddress(port)); // 獲得一個通道管理器 this.selector = Selector.open(); // 將通道管理器和該通道綁定,并為該通道注冊SelectionKey.OP_ACCEPT事件,注冊該事件后, // 當(dāng)該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } public void listen() throws IOException { System.out.println("服務(wù)端啟動成功!"); // 輪詢訪問selector while (true) { // 當(dāng)注冊的事件到達時,方法返回;否則,該方法會一直阻塞 int select = selector.select(); if (select == 0) { continue; } // 獲得selector中選中的項的迭代器,選中的項為注冊的事件 Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重復(fù)處理 ite.remove(); if (key.isAcceptable()) {// 客戶端請求連接事件 ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 獲得和客戶端連接的通道 SocketChannel channel = server.accept(); // 設(shè)置成非阻塞 channel.configureBlocking(false); // 在和客戶端連接成功之后,為了可以接收到客戶端的信息,需要給通道設(shè)置讀的權(quán)限。 channel.register(this.selector, SelectionKey.OP_READ); } else if (key.isReadable()) {// 獲得了可讀的事件 read(key); } } } } public void read(SelectionKey key) throws IOException { // 服務(wù)器可讀取消息:得到事件發(fā)生的Socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 創(chuàng)建讀取的緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(512); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服務(wù)端收到信息:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8")); channel.write(outBuffer);// 將消息回送給客戶端 } public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8000); server.listen(); } }
Redis為什么支持高并發(fā)
Redis官方?jīng)]有windows版本redis,只有l(wèi)inux版本的reids。
Redis的底層是采用nio 多路io復(fù)用機制實現(xiàn)對多個不同的連接(tcp)實現(xiàn)io的復(fù)用;能夠非常好的支持高并發(fā),同時能夠先天性支持線程安全的問題。為什么現(xiàn)場安全?因為使用一個線程維護多個不同的io操作 原理使用nio的選擇器,將多個不同的Channel統(tǒng)一交給我們的selector(選擇器管理)。
但是nio的實現(xiàn)在不同的操作系統(tǒng)上存在差別:在我們windows操作系統(tǒng)上使用 select 實現(xiàn)輪訓(xùn)機制、在linux操作系統(tǒng)使用epoll
備注:windows操作系統(tǒng)是沒有epoll
在windows操作系統(tǒng)中使用select實現(xiàn)輪訓(xùn)機制時間復(fù)雜度是為 o(n),而且這種情況也會存在空輪訓(xùn)的情況,效率非常低、其次默認對我們的輪訓(xùn)有一定限制,所以這樣的話很難支持上萬tcp連接。
所以在這時候linux操作就出現(xiàn)epoll實現(xiàn)事件驅(qū)動回調(diào)形式通知,不會存在空輪訓(xùn)的情況,只是對活躍的socket實現(xiàn)主動回調(diào),這樣的性能有很大的提升 所以時間復(fù)雜度為是o(1)
注意:windows操作系統(tǒng)沒有epoll、只有l(wèi)inux操作系統(tǒng)有。
所以為什么Nginx、redis能夠支持非常高的并發(fā) 最終都是靠的linux版本的 io 多路復(fù)用機制epoll
到此這篇關(guān)于Netty與NIO超詳細講解的文章就介紹到這了,更多相關(guān)Netty NIO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編程實現(xiàn)多線程TCP服務(wù)器完整實例
這篇文章主要介紹了Java編程實現(xiàn)多線程TCP服務(wù)器完整實例,具有一定借鑒價值,需要的朋友可以參考下2018-01-01Java?Web實現(xiàn)簡易圖書管理系統(tǒng)
這篇文章主要為大家詳細介紹了Java?Web實現(xiàn)簡易圖書管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-09-09java ThreadLocal線程局部變量常用方法使用場景示例詳解
這篇文章主要介紹了為大家java ThreadLocal線程局部變量常用方法使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07java?數(shù)組越界判斷和獲取數(shù)組長度的實現(xiàn)方式
這篇文章主要介紹了java?數(shù)組越界判斷和獲取數(shù)組長度的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12mybatis interceptor 處理查詢參數(shù)及查詢結(jié)果的實例代碼
這篇文章主要介紹了mybatis interceptor 處理查詢參數(shù)及查詢結(jié)果,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-01-01