Netty與NIO超詳細(xì)講解
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)信號(hào)驅(qū)動(dòng)I/O (signal driven I/O (SIGIO))
5)異步I/O (asynchronous I/O (the POSIX aio_functions))
前面四種都是同步io、第五種是異步IO;
阻塞式:當(dāng)程序沒(méi)有獲取到數(shù)據(jù)的時(shí)候,整個(gè)應(yīng)用可能會(huì)產(chǎn)生阻塞,放棄了CPU執(zhí)行,無(wú)法去做其他事情。
非阻塞式:不管有沒(méi)有獲取到數(shù)據(jù),都必須立馬返回一個(gè)結(jié)果,如果沒(méi)有獲取到數(shù)據(jù)的情況下返回一個(gè)錯(cuò)誤標(biāo)記,根據(jù)錯(cuò)誤的標(biāo)記不斷輪詢。BIO屬于阻塞式的io操作??梢允褂枚嗑€程實(shí)現(xiàn)異步I0,同時(shí)處理多個(gè)請(qǐng)求。缺點(diǎn):非常消耗服務(wù)器資源CPU
阻塞IO的流程
BIO屬于阻塞式的io操作。
可以使用多線程實(shí)現(xiàn)異步IO,同時(shí)處理多個(gè)請(qǐng)求。缺點(diǎn):非常消耗服務(wù)器資源CPU

當(dāng)我們?cè)谡{(diào)用一個(gè)io函數(shù)的時(shí)候,如果沒(méi)有獲取到數(shù)據(jù)的情況下,那么就會(huì)一直等待;等待的過(guò)程中會(huì)導(dǎo)致整個(gè)應(yīng)用程序一直是一個(gè)阻塞的過(guò)程,無(wú)法去做其他的實(shí)現(xiàn)。
IO復(fù)用
IO復(fù)用機(jī)制:IO實(shí)際指的就是網(wǎng)絡(luò)的IO、多路也就是多個(gè)不同的tcp連接;復(fù)用也就是指使用同一個(gè)線程合并處理多個(gè)不同的IO操作,這樣的話可以減少CPU資源。
信號(hào)驅(qū)動(dòng)I/O
發(fā)出一個(gè)請(qǐng)求實(shí)現(xiàn)觀察監(jiān)聽(tīng),當(dāng)有數(shù)據(jù)的時(shí)候直接走我們異步回調(diào);
異步IO
異步io也就是發(fā)出請(qǐng)求數(shù)據(jù)之后,剩下的事情完全實(shí)現(xiàn)異步完成
同步與異步
站在多線程的角度總結(jié):
同步整個(gè)應(yīng)用代碼執(zhí)行順序是從上往下執(zhí)行 并且返回到結(jié)果;
異步:開(kāi)啟多個(gè)不同的分支實(shí)現(xiàn)并行執(zhí)行 每個(gè)線程互不影響;
站在web項(xiàng)目角度分析
默認(rèn)的情況Http請(qǐng)求就是一個(gè)同步形式實(shí)現(xiàn)調(diào)用 基于請(qǐng)求與響應(yīng),如果我們響應(yīng)非常耗時(shí)的話,會(huì)導(dǎo)致客戶端一直等待(用戶體驗(yàn)非常不好)
NIO
Java的nio是在Jdk1.4版本之后推出了一套新的io方案,這種io方案對(duì)原有io做了一次性能上的升級(jí)
NIO翻譯成英文 no blocking io 簡(jiǎn)稱為 nio 非阻塞io,不是new io。
比傳統(tǒng)的io支持了面向緩沖區(qū)、基于通道實(shí)現(xiàn)的io的方案。
BIO 與 NIO 區(qū)別:
Bio是一個(gè)阻塞式的io,它是西向與流傳輸也就是根據(jù)每個(gè)字節(jié)實(shí)現(xiàn)傳輸效率非常低,
而我們的nio是雨向與緩沖區(qū)的非阻塞邊.其中最大的亮點(diǎn):運(yùn)多路復(fù)用機(jī)制,
I0多路復(fù)用
多路實(shí)際上指的是多個(gè)不同的 tcp 連接。
復(fù)用:一個(gè)線程可以維護(hù)多個(gè)不同的io操作。
優(yōu)點(diǎn):占用cpu資源非常小、保證線程安全問(wèn)題。
IO 多路復(fù)用實(shí)現(xiàn)原理
使用一個(gè)Java案例來(lái)描述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)建一個(gè)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所有的操作都是通過(guò)通道開(kāi)始的,所有的通道都會(huì)注冊(cè)到統(tǒng)一個(gè)選擇器(Selector)上實(shí)現(xiàn)管理,在通過(guò)選擇器將數(shù)據(jù)統(tǒng)一寫入到 buffer中。
緩沖區(qū)(Buffer)
Buffer本質(zhì)上就是一塊內(nèi)存區(qū),可以用來(lái)讀取數(shù)據(jù),也就先將數(shù)據(jù)寫入到緩沖區(qū)中、在統(tǒng)一的寫入到硬盤上。
選擇器(Selector)
Selector可以稱做為選擇器,也可以把它叫做多路復(fù)用器,可以在單線程的情況下可以去維護(hù)多個(gè)Channel,也可以去維護(hù)多個(gè)連接;
使用Java原生API實(shí)現(xiàn)NIO操作
public class NIOServer {
/**
* 創(chuàng)建一個(gè)選擇器
*/
private Selector selector;
public void initServer(int port) throws IOException {
// 獲得一個(gè)ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 設(shè)置通道為非阻塞
serverSocketChannel.configureBlocking(false);
// 將該通道對(duì)應(yīng)的ServerSocket綁定到port端口
serverSocketChannel.bind(new InetSocketAddress(port));
// 獲得一個(gè)通道管理器
this.selector = Selector.open();
// 將通道管理器和該通道綁定,并為該通道注冊(cè)SelectionKey.OP_ACCEPT事件,注冊(cè)該事件后,
// 當(dāng)該事件到達(dá)時(shí),selector.select()會(huì)返回,如果該事件沒(méi)到達(dá)selector.select()會(huì)一直阻塞。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException {
System.out.println("服務(wù)端啟動(dòng)成功!");
// 輪詢?cè)L問(wèn)selector
while (true) {
// 當(dāng)注冊(cè)的事件到達(dá)時(shí),方法返回;否則,該方法會(huì)一直阻塞
int select = selector.select();
if (select == 0) {
continue;
}
// 獲得selector中選中的項(xiàng)的迭代器,選中的項(xiàng)為注冊(cè)的事件
Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 刪除已選的key,以防重復(fù)處理
ite.remove();
if (key.isAcceptable()) {// 客戶端請(qǐng)求連接事件
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ù)用機(jī)制實(shí)現(xiàn)對(duì)多個(gè)不同的連接(tcp)實(shí)現(xiàn)io的復(fù)用;能夠非常好的支持高并發(fā),同時(shí)能夠先天性支持線程安全的問(wèn)題。為什么現(xiàn)場(chǎng)安全?因?yàn)槭褂靡粋€(gè)線程維護(hù)多個(gè)不同的io操作 原理使用nio的選擇器,將多個(gè)不同的Channel統(tǒng)一交給我們的selector(選擇器管理)。
但是nio的實(shí)現(xiàn)在不同的操作系統(tǒng)上存在差別:在我們windows操作系統(tǒng)上使用 select 實(shí)現(xiàn)輪訓(xùn)機(jī)制、在linux操作系統(tǒng)使用epoll
備注:windows操作系統(tǒng)是沒(méi)有epoll
在windows操作系統(tǒng)中使用select實(shí)現(xiàn)輪訓(xùn)機(jī)制時(shí)間復(fù)雜度是為 o(n),而且這種情況也會(huì)存在空輪訓(xùn)的情況,效率非常低、其次默認(rèn)對(duì)我們的輪訓(xùn)有一定限制,所以這樣的話很難支持上萬(wàn)tcp連接。
所以在這時(shí)候linux操作就出現(xiàn)epoll實(shí)現(xiàn)事件驅(qū)動(dòng)回調(diào)形式通知,不會(huì)存在空輪訓(xùn)的情況,只是對(duì)活躍的socket實(shí)現(xiàn)主動(dòng)回調(diào),這樣的性能有很大的提升 所以時(shí)間復(fù)雜度為是o(1)
注意:windows操作系統(tǒng)沒(méi)有epoll、只有l(wèi)inux操作系統(tǒng)有。
所以為什么Nginx、redis能夠支持非常高的并發(fā) 最終都是靠的linux版本的 io 多路復(fù)用機(jī)制epoll
到此這篇關(guān)于Netty與NIO超詳細(xì)講解的文章就介紹到這了,更多相關(guān)Netty NIO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Mybatis框架Dao層的實(shí)現(xiàn)與映射文件以及核心配置文件詳解分析
MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過(guò)簡(jiǎn)單的 XML 或注解來(lái)配置和映射原始類型、接口和 Java POJO為數(shù)據(jù)庫(kù)中的記錄2021-10-10
一文總結(jié) Shiro 實(shí)戰(zhàn)教程
shiro是apache的一個(gè)開(kāi)源框架,是一個(gè)權(quán)限管理的框架,實(shí)現(xiàn) 用戶認(rèn)證、用戶授權(quán),這篇文章詳細(xì)總結(jié)了shiro用法,感興趣的同學(xué)可以參考閱讀2023-04-04
java 使用ImageIO.writer從BufferedImage生成jpeg圖像遇到問(wèn)題總結(jié)及解決
這篇文章主要介紹了java 使用ImageIO.writer從BufferedImage生成jpeg圖像遇到問(wèn)題總結(jié)及解決的相關(guān)資料,需要的朋友可以參考下2017-03-03
SpringBoot中使用Zookeeper實(shí)現(xiàn)分布式鎖的案例
本文主要介紹了SpringBoot中使用Zookeeper實(shí)現(xiàn)分布式鎖的案例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
在Struts2中如何將父類屬性序列化為JSON格式的解決方法
本篇文章,小編將為大家介紹關(guān)于在Struts2中如何將父類屬性序列化為JSON格式的解決方法,有需要的朋友可以參考一下2013-04-04
Spring Data JPA帶條件分頁(yè)查詢實(shí)現(xiàn)原理
這篇文章主要介紹了Spring Data JPA帶條件分頁(yè)查詢實(shí)現(xiàn)原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05

