寫在前面,這里所說的IO主要是強調的網絡IO
1.BIO(同步並阻塞)
客戶端一個請求對應一個線程。客戶端上來一個請求(最開始的連接以及后續的IO請求),服務端新建一個線程去處理這個請求,由於線程總數是有限的(操作系統對線程總數的限制或者線程池的大小),所以,當達到最大值時給客戶端的反饋就是無法響應,阻塞體現在服務端接收客戶端連接請求被阻塞了,還有一種阻塞是在單線程處理某一個連接時,需要一直等待IO操作完成。同步體現在單個線程處理請求時調用read(write)方法需等待讀取(寫)操作完成才能返回。
2.NIO(同步非阻塞)
客戶端一個IO請求對應一個線程。過程是這樣的,每個客戶端一開始上來的連接會注冊到selector中,selector會輪詢注冊上來的連接是否有IO請求,如果有IO請求,就創建一個線程處理該連接上的該次請求。非阻塞體現在服務端能夠無限量(相對於BIO)的接收客戶端的連接請求。同步體現在單個線程處理請求時調用read(write)方法需等待讀取(寫)操作完成才能返回。這種模式下,如果后端應用處理遇到資源爭奪(數據庫操作)而阻塞等,為提高請求的處理速度,可以在后端設立資源池或隊列等,把對應的請求數據以及現場(哪個連接的哪個請求等)放入隊列,前台線程立即返回處理別的IO請求。
3.AIO(NIO2)(異步阻塞)
客戶端一個IO請求對應一個線程。過程同NIO,只是在讀寫IO時略有差異,對於read,方法調用后會立即返回,返回對應中有個回調方法,調用時java會告知操作系統緩沖區大小以及地址,操作系統把流里面的內容讀入緩沖區后回調剛剛read返回的回調方法。對應write,方法調用后會立即返回,返回對應中有個回調方法,調用時將數據放入緩存區,操作系統往流里面寫完數據后同樣會回調剛剛wirte返回的回調方法。阻塞是因為此時是通過select系統調用來完成的,而select函數本身的實現方式是阻塞的,而采用select函數有個好處就是它可以同時監聽多個文件句柄,從而提高系統的並發性請求。異步步體現在單個線程處理請求時調用read(write)方法會立即返回,返回對象有回調方法,底層OS完成IO操作后會回調該方法。
適用場景:
-
BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,並發局限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
-
NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,並發局限於應用中,編程比較復雜,JDK1.4開始支持。
-
AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與並發操作,編程比較復雜,JDK7開始支持。
在JDK1.7中,這部分內容被稱作NIO.2,主要在java.nio.channels包下增加了下面四個異步通道:
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel
注:I/O屬於底層操作,需要操作系統支持,並發也需要操作系統的支持,所以性能方面不同操作系統差異會比較明顯。
在高性能的I/O設計中,有兩個比較著名的模式Reactor和Proactor模式,其中Reactor模式用於同步I/O,而Proactor運用於異步I/O操作。
附錄:java NIO示例
服務端:
package cn.nio;
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;
/**
* NIO服務端
*
*/
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 cn.nio;
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;
/**
* NIO客戶端
*
*/
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方法一樣
}
/**
* 啟動客戶端測試
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOClient client = new NIOClient();
client.initClient("localhost",8000);
client.listen();
}
}
