NIO與BIO的區別


簡介

BIO:同步阻塞式IO,服務器實現模式為一個連接一個線程,即客戶端發送請求服務器端就需要啟動一個線程處理,若這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。
NIO:同步非阻塞式IO,服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器(采用事件驅動思想實現)上,多路復用器輪詢I/O請求時才啟動一個線程進行處理。
AIO(NIO.2):異步非阻塞式IO,服務器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啟動線程進行處理。 

不同點與共同點

共同點:兩者都是同步操作。即必須先進行IO操作后才能進行下一步操作。

不同點:BIO多線程對某資源進行IO操作時會出現阻塞,即一個線程進行IO操作完才會通知另外的IO操作線程,必須等待。

               NIO多線程對某資源進行IO操作時會把資源先操作至內存緩沖區。然后詢問是否IO操作就緒,是則進行IO操作,否則進行下一步操作,然后不斷的輪詢是否IO操作就緒,直到IO操作就緒后進行相關操作。

 

              nio 適合大量長連接,而且大部分只 hold 不處理的場景,如果你能將你的項目異步化的話 nio 肯定比 bio 扛得多。你用 bio 其實壓測時是打不滿 CPU 的,所以采用 nio 來壓榨 CPU,如果你 bio 都能打滿 CPU,那就沒必要搞 nio 和異步化了,因為物理極限了,沒啥好搞的了,只能去優化代碼。

BIO
     單線程:同步阻塞式IO在while循環中服務端會調用accept方法等待接收客戶端的連接請求,一旦接收到一個連接請求,就可以建立通信套接字,在這個通信套接字上進行讀寫操作,此時不能接收其他客戶端的連接請求,只能等待同當前連接的客戶端的操作執行完成。

     多線程:如果BIO要能夠同時處理多個客戶端請求,就必須使用多線程,即每次accept阻塞等待來自客戶端請求,一旦受到連接請求就建立通信套接字,同時開啟一個新的線程來出爐這個套接字的數據讀寫請求,然后又立刻繼續accept等待其他客戶端連接請求,即為每個客戶端請求都單獨創建一個線程來單獨處理

雖然此時服務器具備了高並發能力,即能夠同時處理多個客戶端請求了,但是卻帶來了一個問題,隨着開啟的線程數目增多,將會消耗過多的內存資源,導致服務器變慢甚至崩潰,NIO可以一定程度解決這個問題。


NIO

1.建立連接:若服務端監聽到客戶端到連接請求,便為其建立通信套接字(java中就是通道(Channel),然后返回繼續監聽,若同時有多個客戶端連接請求到來也可以全部接收,依次為它們建立通信套接字
2.處理數據:若服務端監聽到來自自己已經創建了通信套接字到客戶端發來的數據,就會調用對應的接口處理接收到的數據,若同時有多個客戶端發來數據也可以依次進行處理
3.同時監聽:監聽多個客戶端的連接請求和接收數據請求的同時,還能監聽自己有數據發送

總之就是在一個線程中就可以調用多路復用接口(java中是select)阻塞同時監聽來自多個客戶端的IO請求,一旦有收到IO請求就調用對應函數處理。

AIO

      與NIO不同,當進行讀寫操作時,只需要直接調用API的read或write方法即可。這兩種方法均為異步的,對於讀操作而言,當有流可讀取的時,操作系統就會將可讀的流傳入read方法的緩沖區,並通知應用程序。對於寫操作而言,當操作系統將write方法傳遞的流寫入完畢時,操作系統主動通知應用程序。既可以理解為,read/write方法都是異步的,完成后會主動調用回調函數。在JDK7中,這部分內容被稱為NIO.2,主要在java.nio.channels包下增加了四個異步通道。

AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel


各自應用場景

到這里你也許已經發現,一旦有請求到來(不管是幾個同時到還是只有一個到),都會調用對應IO處理函數處理,所以:

(1)NIO適合處理連接數目特別多,但是連接比較短(輕操作)的場景,Jetty,Mina,ZooKeeper等都是基於java nio實現。

(2)BIO方式適用於連接數目比較小且固定的場景,這種方式對服務器資源要求比較高,並發局限於應用中。

(3)AIO方式使用於連接數目比較多且比較長(重操作)的架構,比如相冊服務器,充分調用OS參與並發操作,編程比較復雜。

 

以下是簡單的NIO實現代碼:

服務端代碼:

package com.demo;
import java.io.IOException;
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 {
//通道管理器
private Selector selector;

/**
* 獲得一個ServerSocket通道,並對該通道做一些初始化的工作
* @param port 綁定的端口號
* @throws IOException
*/
public void initServer(int port) throws IOException {
// 獲得一個ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 設置通道為非阻塞
serverChannel.configureBlocking(false);
// 將該通道對應的ServerSocket綁定到port端口
serverChannel.socket().bind(new InetSocketAddress(port));
// 獲得一個通道管理器
this.selector = Selector.open();
//將通道管理器和該通道綁定,並為該通道注冊SelectionKey.OP_ACCEPT事件,注冊該事件后,
//當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}

/**
* 采用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void listen() throws IOException {
System.out.println("服務端啟動成功!");
// 輪詢訪問selector
while (true) {
//當注冊的事件到達時,方法返回;否則,該方法會一直阻塞
selector.select();
// 獲得selector中選中的項的迭代器,選中的項為注冊的事件
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 刪除已選的key,以防重復處理
ite.remove();
// 客戶端請求連接事件
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key
.channel();
// 獲得和客戶端連接的通道
SocketChannel channel = server.accept();
// 設置成非阻塞
channel.configureBlocking(false);

//在這里可以給客戶端發送信息哦
channel.write(ByteBuffer.wrap(new String("向客戶端發送了一條信息").getBytes()));
//在和客戶端連接成功之后,為了可以接收到客戶端的信息,需要給通道設置讀的權限。
channel.register(this.selector, SelectionKey.OP_READ);

// 獲得了可讀的事件
} else if (key.isReadable()) {
read(key);
}

}

}
}
/**
* 處理讀取客戶端發來的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException{
// 服務器可讀取消息:得到事件發生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 創建讀取的緩沖區
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服務端收到信息:"+msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);// 將消息回送給客戶端
}

/**
* 啟動服務端測試
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NioServer server = new NioServer();
server.initServer(8000);
server.listen();
}
}

客戶端代碼:

package com.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioClient {
//通道管理器
private Selector selector;

/**
* 獲得一個Socket通道,並對該通道做一些初始化的工作
* @param ip 連接的服務器的ip
* @param port 連接的服務器的端口號
* @throws IOException
*/
public void initClient(String ip,int port) throws IOException {
// 獲得一個Socket通道
SocketChannel channel = SocketChannel.open();
// 設置通道為非阻塞
channel.configureBlocking(false);
// 獲得一個通道管理器
this.selector = Selector.open();

// 客戶端連接服務器,其實方法執行並沒有實現連接,需要在listen()方法中調
//用channel.finishConnect();才能完成連接
channel.connect(new InetSocketAddress(ip,port));
//將通道管理器和該通道綁定,並為該通道注冊SelectionKey.OP_CONNECT事件。
channel.register(selector, SelectionKey.OP_CONNECT);
}

/**
* 采用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void listen() throws IOException {
// 輪詢訪問selector
while (true) {
selector.select();
// 獲得selector中選中的項的迭代器
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 刪除已選的key,以防重復處理
ite.remove();
// 連接事件發生
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key
.channel();
// 如果正在連接,則完成連接
if(channel.isConnectionPending()){
channel.finishConnect();

}
// 設置成非阻塞
channel.configureBlocking(false);

//在這里可以給服務端發送信息哦
channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes()));
//在和服務端連接成功之后,為了可以接收到服務端的信息,需要給通道設置讀的權限。
channel.register(this.selector, SelectionKey.OP_READ);

// 獲得了可讀的事件
} else if (key.isReadable()) {
read(key);
}

}

}
}
/**
* 處理讀取服務端發來的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException{
//和服務端的read方法一樣
// 得到事件發生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 創建讀取的緩沖區
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("客戶端收到信息:"+msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);// 將消息回送給服務端
}


/**
* 啟動客戶端測試
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NioClient client = new NioClient();
client.initClient("localhost",8000);
client.listen();
}
}

部分原文摘抄自:https://blog.csdn.net/weixin_42133940/article/details/88073132

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM