java io學習
1 什麼是io
我們常說的io是文件或字符的輸入(input)和輸出(output)
2 BIO,NIO,AIO 有什麼區別
2.1 BIO
什麼是BIO?
同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。
在活動連接數不是特別高(小於單機1000)的情況下,這種模型是比較不錯的,可以讓每一個連接專注於自己的 I/O 並且編程模型簡單,也不用過多考慮系統的過載、限流等問題。線程池本身就是一個天然的漏斗,可以緩衝一些系統處理不了的連接或請求。
但是,當面對十萬甚至百萬級連接的時候,傳統的 BIO 模型是無能爲力的。
一個java BIO實例如下
服務端代碼展示
public class BioServerDemo {
public static void main(String[] args) {
int port = 9999;
try {
// 創建 bio socket服務
ServerSocket serverSocket = new ServerSocket();
// 綁定服務端口號
serverSocket.bind(new InetSocketAddress(port));
while (true){
// 阻塞獲得客戶端socket
Socket socket = serverSocket.accept();
// 進入自定義的handle方法,處理客戶端的socket
new Thread(()-> handle(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 自定義handle方法,處理服務端和客戶端的socket
private static void handle(Socket socket) {
byte[] bytes = new byte[1024];
try {
// 獲得輸入流,讀出客戶端發送的數據
int len = socket.getInputStream().read(bytes);
System.out.println(new String(bytes,0,len));
// 獲得輸出流,回寫給客戶端數據
socket.getOutputStream().write("this is server".getBytes());
socket.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客戶端代碼展示
public class BioClientDemo {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 9999;
try {
Socket socket = new Socket(host,port);
// 獲得輸出流,向服務端寫數據
socket.getOutputStream().write("hello,this is client".getBytes());
socket.getOutputStream().flush();
// 獲得輸入流,讀取服務端給客戶端的數據
byte[] bytes = new byte[1024];
int len = socket.getInputStream().read(bytes);
System.out.println(new String(bytes,0,len));
} catch (IOException e) {
e.printStackTrace();
}
}
}
優點: 模型簡單,編碼簡單
缺點:性能較低
小結:
1 BIO 主要體現在 獲得客戶端連接socket的時候,在阻塞accept,在一個socket中進行讀寫時,也會一直阻塞,會一直等待這個線程處理完結束
2 每個連接都會創建一個線程,隨着線程數量的增加,CPU切換線程上下文的消耗也隨之增加,在高過某個閥值後,繼續增加線程,性能不增反降
3 因爲一個連接創建一個線程,模型比較簡單,適用於客戶端連接教少的場景,可以進行簡單處理
2.2 NIO
什麼是NIO?
NIO 稱爲 Non-Block IO, 它支持面向緩衝的,基於通道的I/O操作方法。
NIO提供了與傳統BIO模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現,兩種通道都支持阻塞和非阻塞兩種模式。
阻塞模式使用就像傳統中的支持一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。
含有選擇器,選擇器用於使用單個線程處理多個通道。
它需要較少的線程來處理這些通道。線程之間的切換對於操作系統來說是昂貴的。
爲了提高系統效率選擇器是有用的。對於低負載、低併發的應用程序,可以使用同步阻塞I/O來提升開發速率和更好的維護性;對於高負載、高併發的(網絡)應用,應使用 NIO 的非阻塞模式來開發
線程模型示例
會有一個線程作爲selector(選擇器)進行處理所有的客戶端,通過事件監聽的形式處理socketChannel
java NIO 單線程實現
服務端代碼
public class NioServerDemo {
public static void main(String[] args) throws IOException {
int port = 9999;
// 創建 nio server
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
System.out.println("server start");
// 獲得selector
Selector selector = Selector.open();
// 把selector和accept事件註冊到server中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
selector.select();
// 獲得selector管理的所有事件
Set<SelectionKey> selectors = selector.selectedKeys();
// 迭代selector
Iterator<SelectionKey> selectionKeyIterable = selectors.iterator();
while (selectionKeyIterable.hasNext()){
SelectionKey key = selectionKeyIterable.next();
// 處理每一個 selectionKey
handle(key);
// 處理完之後移除
selectionKeyIterable.remove();
}
}
}
private static void handle(SelectionKey key){
// 處理accept事件
if (key.isAcceptable()){
// 獲得serversocket channel,處理對客戶端連接的socket
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
try {
SocketChannel socketChannel = serverSocketChannel.accept();
serverSocketChannel.configureBlocking(false);
// 註冊讀事件
serverSocketChannel.register(key.selector(),SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}else if (key.isReadable()){
// 處理讀事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.clear();
int len = 0;
try {
len = socketChannel.read(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
if (len != -1){
System.out.println(new String(byteBuffer.array(),0,len));
}
// 向客戶端寫入數據
ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
try {
socketChannel.write(bufferWrite);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多線程實現
public class NioThreadPoolServerDemo {
private ExecutorService executorService = Executors.newFixedThreadPool(20);
private Selector selector;
public static void main(String[] args) throws IOException {
int port = 9999;
NioThreadPoolServerDemo nioThreadPoolServerDemo = new NioThreadPoolServerDemo();
nioThreadPoolServerDemo.initServer(port);
nioThreadPoolServerDemo.listen();
}
private void initServer(int port) throws IOException {
// 創建bio 服務
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 開啓selector 模型
this.selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
private void listen() throws IOException {
while (true){
selector.select();
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
if (selectionKeyIterator.hasNext()){
SelectionKey key = selectionKeyIterator.next();
selectionKeyIterator.remove();
// 處理accept事件
if (key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel sc = server.accept();
sc.configureBlocking(false);
// 註冊read事件
sc.register(this.selector,SelectionKey.OP_READ);
}else if (key.isReadable()){
key.interestOps(SelectionKey.OP_READ);
// 有數據讀寫時,交給線程池處理,
executorService.execute(new ThreadHandlerChannel(key));
}
}
}
}
}
class ThreadHandlerChannel extends Thread{
private SelectionKey key;
ThreadHandlerChannel(SelectionKey key){
this.key = key;
}
@Override
public void run() {
// 處理讀事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.clear();
int len = 0;
try {
len = socketChannel.read(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
if (len != -1){
System.out.println(new String(byteBuffer.array(),0,len));
}
// 向客戶端寫入數據
ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
try {
socketChannel.write(bufferWrite);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
小結
1 通過選擇器selector進行事件監聽
2 需要主動把socket事件註冊到選擇器中
2.3 AIO (asynchronius)
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。
異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。
AIO 是異步IO的縮寫,雖然 NIO 在網絡操作中,提供了非阻塞的方法,但是 NIO 的 IO 行爲還是同步的。
對於 NIO 來說,我們的業務線程是在 IO 操作準備好時,得到通知,接着就由這個線程自行進行 IO 操作,IO操作本身是同步的。
public class AioServerDemo {
public static void main(String[] args) throws IOException {
int port = 9999;
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(port));
// 當有客戶端連接時,accept不再阻塞線程,交給CompletionHandler 進行處理
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object attachment) {
serverSocketChannel.accept(null,this);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 讀數據也不再阻塞執行,讀完數據後交給CompletionHandler處理
client.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
client.write(ByteBuffer.wrap("this is server".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {}
});
}
@Override
public void failed(Throwable exc, Object attachment) {}
});
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
小結:
1 NIO和AIO在linux底層都是用epoll實現
2 windows上AIO用的是系統實現
3 使用場景
1 BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
2 NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
3 AIO方式適用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。