java I/O 模型簡述

概述

從同步與異步&阻塞與非阻塞的概念,到具體的I/O模型,再到具體的Java語言實現,都是層層遞進,本篇就從Java語言來看I/O模型的大概狀況。html

整個Java I/O模型,大體能夠分爲三類java

  • BIO:JDK1.4以前的阻塞IO
  • NIO:JDK1.4及之後的版本非阻塞IO
  • AIO:JDK1.7以後,又叫NIO.2

1、BIO阻塞IO

一、基本概念

BIO,即爲Blocking I/O,阻塞IO,大體流程爲:linux

  • 一、服務端創建ServerSocket,以一個端口啓動
  • 二、等待客戶端創建socket鏈接,若是沒有鏈接,一直阻塞
  • 三、一個socket創建鏈接以後,從線程池中去一個線程取處理socket

二、代碼分析

public class BlockingIOServer {
	public static void main(String[] args) throws IOException {
		int port = 10000;
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		ServerSocket server = new ServerSocket(port);

		while(true){
			Socket client = server.accept();
			
			//從線程池取線程處理client
			threadPool.execute(()->{
				try{
					InputStream input = client.getInputStream();
					
					//TODO read input
					String req = null;
					String res = "response:"+req;
					
					//TODO response
					client.getOutputStream().write(res.getBytes());
					
				}catch(IOException e){
					e.printStackTrace();
				}finally {
					try {
						client.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
		}
	}

}

三、總結

  • 若是請求量過大,線程池不夠用,那麼會嚴重影響性能。CPU疲於切換線程,執行的效率下降。git

  • 如今tomcat I/O模型默認仍是BIO。編程

  • 可是鏈接不大,該模型仍是很是具備優越性,代碼編寫簡單,只須要關注該線程內的鏈接便可。windows

  • BIO模型,也就是同步阻塞模型。tomcat

2、NIO非阻塞IO

一、基本概念

  • NIO,便是Non Blocking I/O,非阻塞IO。服務器

  • 在JDK1.4及之後版本中提供了一套API來專門操做非阻塞I/O,接口以及類定義在java.nio包。因爲這套API是JDK新提供的I/O API,所以,也叫New I/O。網絡

  • NIO API由四個主要的部分組成:緩衝區(Buffers)、通道(Channels)、選擇器(Selector)和非阻塞I/O的核心類組成。多線程

NIO 的工做大體流程爲:

  • 一、通道註冊一個監聽到事件處理器
  • 二、有事件發生時,事件處理器會通知相應的通道處理

二、代碼分析

public class NonBlockingIOServer {
	
	private  int BLOCK = 4096;
	private  ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
	private  ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
	private  Selector selector;
	
	
	public NonBlockingIOServer(int port) throws IOException {
		//1.open  ServerSocketChannel
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		//2.configureBlocking false
		serverSocketChannel.configureBlocking(false);
		//3.bind port
		serverSocketChannel.socket().bind(new InetSocketAddress(port));
		
		//4.open  Selector
		selector = Selector.open();
		//5.serverSocketChannel register select
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		System.out.println("Server Start,port:"+port);
	}

	private void accept() throws IOException {
		while (true) {
			// 1.select,block
			selector.select();
			
			// 2.SelectionKey iterator
			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while (iterator.hasNext()) {
				SelectionKey selectionKey = iterator.next();
				iterator.remove();
				try {
					doAccept(selectionKey);
				} catch (IOException e) {
					selectionKey.cancel();
					e.printStackTrace();
				}
			}
		}
	}

	private void doAccept(SelectionKey selectionKey)throws IOException{
		if (selectionKey.isAcceptable()) {
			// ServerSocketChannel 的 selectionKey
			ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
			if(null == server){
				return;
			}
			
			//接受到此通道套接字的鏈接,block here
			SocketChannel client = server.accept(); 
			// 配置爲非阻塞
			client.configureBlocking(false);   
			
			// 註冊讀到selector,等待讀的selectionKey
			client.register(selector, SelectionKey.OP_READ);
		} else if (selectionKey.isReadable()) {
			// SocketChannel 的 selectionKey
			SocketChannel client = (SocketChannel) selectionKey.channel();
			
			receiveBuffer.clear();
			int count = client.read(receiveBuffer);	
			if (count > 0) {
				String receiveText = new String( receiveBuffer.array(),0,count);
				System.out.println(receiveText);
				//註冊寫到selector,等待讀的selectionKey
				SelectionKey key = client.register(selector, SelectionKey.OP_WRITE);
				//這裏能夠做爲設計框架的擴展之處
				key.attach(receiveText);
			}
		} else if (selectionKey.isWritable()) {
			// SocketChannel selectionKey
			SocketChannel client = (SocketChannel) selectionKey.channel();
			
			//取出read 的 attachment
			String request = (String) selectionKey.attachment();
			String sendText="response--" + request;
			
			sendBuffer.clear();
			sendBuffer.put(sendText.getBytes());
			sendBuffer.flip();
			
			//輸出到通道
			client.write(sendBuffer);
			System.out.println(sendText);
			client.register(selector, SelectionKey.OP_READ);
		}
	}

	/**
	 * [[[@param](http://my.oschina.net/u/2303379)](http://my.oschina.net/u/2303379)](http://my.oschina.net/u/2303379) args
	 * [[[@throws](http://my.oschina.net/throws)](http://my.oschina.net/throws)](http://my.oschina.net/throws) IOException
	 */
	public static void main(String[] args) throws IOException {
		int port = 10000;
		NonBlockingIOServer server = new NonBlockingIOServer(port);
		server.accept();
	}
}

主要流程爲:

  • 一、open ServerSocketChannel,configureBlocking false,bind host and port
  • 二、open Selector
  • 三、ServerSocketChannel register on Selector
  • 四、有客戶端鏈接的事件發生,事件處理器通知ServerSocketChannel去處理

三、總結

  • NIO自己是基於事件驅動思想來完成的,便是Reactor模式。

  • 在使用傳統同步I/O模型若是要同時處理多個客戶端請求,就必須使用多線程來處理。也就是說,將每個客戶端請求分配給一個線程來單獨處理。這樣能夠達到咱們的要求,可是若是客戶端的請求過多,服務端程序可能會由於不堪重負而拒絕客戶端的請求,甚至服務器可能會所以而癱瘓。

  • 而NIO基於Selector,當有感興趣的事件發生時,就通知對應的事件處理器去處理事件,若是沒有,則不處理。當socket有流可讀或可寫入socket時,操做系統會相應的通知引用程序進行處理,應用再將流讀取到緩衝區或寫入操做系統。因此使用一個線程作輪詢就能夠了。

  • Buffer,也是NIO的一個新特性,能夠塊狀的讀/寫數據,效率獲得極大的提升。

  • 因此NIO提升了線程的利用率,減小系統在管理線程和線程上下文切換的開銷。

3、AIO異步非阻塞IO

一、基本概念

  • AIO,便是Asynchronous I/O,異步非阻塞I/O
  • JDK1.7以後,引入NIO.2,也叫做AIO,工做方式是異步非阻塞

AIO主要工做流程爲:

  • 客戶端發起一個IO調用
  • 服務端接受IO以後,異步回調接收成功後的IO,不會阻擋當前主流程,主流程繼續接受下一個請求

二、代碼分析

public class AsynchronousIOServer {
	private static Charset charset = Charset.forName("UTF-8");

	public static void main(String[] args) {
		int port = 10000;

		int processors = Runtime.getRuntime().availableProcessors();
		ExecutorService threadPool = Executors.newFixedThreadPool(processors);

		try {
			AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool);
			AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);
			server.bind(new InetSocketAddress(port));

			doAccept(server);

			group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
			System.out.println("close server");
			System.exit(0);
		}
	}

	private static void doAccept(AsynchronousServerSocketChannel server) {
		server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
			@Override
			public void completed(AsynchronousSocketChannel client, Void attachment) {
				server.accept(null, this);// accept next client connect

				doRead(client, attachment);
			}

			@Override
			public void failed(Throwable exc, Void attachment) {
				exc.printStackTrace();
			}
		});

	}

	private static void doRead(AsynchronousSocketChannel client, Void attachment) {
		ByteBuffer buffer = ByteBuffer.allocate(1024);

		client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
			@Override
			public void completed(Integer result, ByteBuffer attachment) {
				if (result <= 0) {
					try {
						System.out.println("客戶端斷線:" + client.getRemoteAddress().toString());
						attachment = null;
					} catch (IOException e) {
						e.printStackTrace();
					}
					return;
				}

				attachment.flip();
				String req = charset.decode(attachment).toString();
				attachment.compact();

				client.read(attachment, attachment, this);// next client read

				/** do service code **/
				System.out.println(req);

				ByteBuffer resBuffer = ByteBuffer.wrap(("response:" + req).getBytes());
				doWrite(client, resBuffer, resBuffer);
			}

			@Override
			public void failed(Throwable exc, ByteBuffer attachment) {
				exc.printStackTrace();
			}

		});
	}

	private static <V> void doWrite(AsynchronousSocketChannel client, ByteBuffer resBuffer, ByteBuffer attachment) {
		client.write(attachment, attachment, new CompletionHandler<Integer, ByteBuffer>() {

			@Override
			public void completed(Integer result, ByteBuffer attachment) {
				// TODO write success

				if (result <= 0) {
					try {
						System.out.println("客戶端斷線:" + client.getRemoteAddress().toString());
						attachment = null;
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}

			@Override
			public void failed(Throwable exc, ByteBuffer attachment) {
				exc.printStackTrace();
			}
		});
	}

}

主要流程爲:

  • 一、建立一個異步非阻塞服務端
  • 二、服務端接受一個請求,異步回調接受成功後的IO請求,而後繼續接受下一個請求
  • 三、異步回調請求的IO,讀取請求數據成功後,異步回調讀取後的結果,而後繼續讀下面的數據,不會阻塞當前IO讀
  • 四、異步回調的讀IO數據,而後同步處理數據,這裏多是計算邏輯,因此這裏也是性能的瓶頸之處,若是是計算密集型,AIO模型不適用,處理完成以後,異步寫數據到IO請求

三、總結

  • 與NIO不一樣,NIO每次都是事件通知,代碼處理時異常複雜,而AIO當進行讀寫操做時,只須直接調用API的read或write方法便可。這兩種方法均爲異步的

  • 對於讀操做而言,當有流可讀取時,操做系統會將可讀的流傳入read方法的緩衝區,並異步回調通知應用程序;

  • 對於寫操做而言,當操做系統將write方法傳遞的流寫入完畢時,操做系統主動通知應用程序。

  • 在JDK1.7中,這部份內容被稱做NIO.2

  • select/poll/epoll/iocp。在Linux 2.6之後,java NIO的實現,是經過epoll來實現的,這點能夠經過jdk的源代碼發現。

  • 而AIO,在windows上是經過IOCP實現的,在linux上仍是經過epoll來實現的。

  • 這裏強調一點:AIO,這是I/O處理模式,而epoll等都是實現AIO的一種編程模型;換句話說,AIO是一種接口標準,各家操做系統能夠實現也能夠不實現。在不一樣操做系統上在高併發狀況下最好都採用操做系統推薦的方式。Linux上尚未真正實現網絡方式的AIO。

4、大總結

一、文中所用代碼

全在這裏

二、三種I/O模型適用場景

  • BIO方式適用於鏈接數量小,鏈接時間短,計算密集,代碼編寫直觀,程序直觀簡單易理解,JDK1.4以前。

  • NIO方式適用於鏈接數量大,鏈接時間短,好比Http服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。

  • AIO方式使用於鏈接數量大,鏈接時間長,IO密集型,好比聊天服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持。

另外要清楚理解的:

  • I/O屬於底層操做,須要操做系統支持,併發也須要操做系統的支持,因此性能方面不一樣操做系統差別會比較明顯。
  • AIO是操做系統準備好數據以後通知應用程序,而NIO是程序不斷的輪詢操做系統是否有準備好數據。

5、文章引用

相關文章
相關標籤/搜索