NIO與IO
1、阻塞IO實例:
public void serve(int portNumber) throws IOException {
//創(chuàng)建一個新的 ServerSocket祥楣,用以監(jiān)聽指定端口上的連接請求
ServerSocket serverSocket = new ServerSocket(portNumber);
//對accept()方法的調(diào)用將被阻塞开财,直到一個連接建立
Socket clientSocket = serverSocket.accept();
//這些流對象都派生于該套接字的流對象汉柒,創(chuàng)建一個緩沖區(qū)存儲輸入流
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
//處理循環(huán)開始
while ((request = in.readLine()) != null) {
if ("Done".equals(request)) {
break;
}
//請求被傳遞給服務(wù)器的處理方法
response = processRequest(request);
//服務(wù)器的響應(yīng)被發(fā)送給了客戶端
out.println(response);
//繼續(xù)執(zhí)行處理循環(huán)
}
}
private String processRequest(String request){
return "Processed";
}
上面的實例只能同時處理一個連接,要管理多個并發(fā)客戶端责鳍,需要為每個新的客戶端socket創(chuàng)建一個新的Thread碾褂,如下圖:
這種方案的影響:
①在任何時候都可能有大量的線程處于休眠狀態(tài),只是等待輸入或者輸出數(shù)據(jù)就緒历葛,這可能算是一種資源浪費正塌。
②需要為每個線程的調(diào)用棧都分配內(nèi)存,其默認值大小區(qū)間為64 KB到1 MB啃洋,具體取決于操作系統(tǒng)传货。
③即使Java虛擬機(JVM)在物理上可以支持非常大數(shù)量的線程屎鳍,但是遠在到達該極限之前宏娄,上下文切換所帶來的開銷就會帶來麻煩,例如逮壁,在達到10 000個連接的時候孵坚。
雖然這種并發(fā)方案對于支撐中小數(shù)量的客戶端來說還算可以接受,但是為了支撐100 000或者更多的并發(fā)連接所需要的資源使得它很不理想
2窥淆、NIO
非阻塞設(shè)計卖宠,其實際上消除了IO的那些弊端。
實例:
public void serve(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
//將服務(wù)器綁定到選定的端口
ss.bind(address);
//打開Selector來處理 Channel
Selector selector = Selector.open();
//將ServerSocket注冊到Selector以接受連接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;){
try {
//等待需要處理的新事件忧饭;阻塞將一直持續(xù)到下一個傳入事件
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
//handle exception
break;
}
//獲取所有接收事件的SelectionKey實例
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
//檢查事件是否是一個新的已經(jīng)就緒可以被接受的連接
if (key.isAcceptable()) {
ServerSocketChannel server =
(ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
//接受客戶端扛伍,并將它注冊到選擇器
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println(
"Accepted connection from " + client);
}
//檢查套接字是否已經(jīng)準(zhǔn)備好寫數(shù)據(jù)
if (key.isWritable()) {
SocketChannel client =
(SocketChannel) key.channel();
ByteBuffer buffer =
(ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
//將數(shù)據(jù)寫到已連接的客戶端
if (client.write(buffer) == 0) {
break;
}
}
//關(guān)閉連接
client.close();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// ignore on close
}
}
}
}
}
圖1-2 使用Selector的非阻塞I/O
class java.nio.channels.Selector是Java的非阻塞I/O實現(xiàn)的關(guān)鍵。它使用了事件通知API以確定在一組非阻塞套接字中有哪些已經(jīng)就緒能夠進行I/O相關(guān)的操作词裤。因為可以在任何的時間檢查任意的讀操作或者寫操作的完成狀態(tài)刺洒,所以如圖1-2所示,一個單一的線程便可以處理多個并發(fā)的連接吼砂。
總體來看逆航,與阻塞I/O模型相比,這種模型提供了更好的資源管理:
使用較少的線程便可以處理許多連接渔肩,因此也減少了內(nèi)存管理和上下文切換所帶來開銷因俐;
當(dāng)沒有I/O操作需要處理的時候,線程也可以被用于其他任務(wù)周偎。
盡管已經(jīng)有許多直接使用Java NIO API的應(yīng)用程序被構(gòu)建了抹剩,但是要做到如此正確和安全并不容易。特別是蓉坎,在高負載下可靠和高效地處理和調(diào)度I/O操作是一項繁瑣而且容易出錯的任務(wù)
參考:《Netty in Action》