一。BIO,NIO,AIO简介

要弄懂这些io模型必须弄懂以下这些概念
同步:的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 比如火车站出站口等待来深圳过年的父母 只有接到父母才能做其他事情 等待过程 不停的电话父母 火车是否到站 是否出站 直到接到父母

异步:指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) 告诉朋友,朋友去接父母 自己忙自己的 朋友接到后 电话通知。(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS) 

阻塞:所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 
直到有东西可读或者可写为止
非阻塞:非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待,

读懂上面几个概念后就可以引入io的模型了

同步阻塞IO(JAVA BIO): 
    同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 

同步非阻塞IO(Java NIO) : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。 

(Java AIO(NIO.2))异步非阻塞IO:  
   在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。    

二。BIO,NIO,AIO编程实现

模拟场景  服务器接受到消息后 发回一个消息  

1.BIO编程实现

BIO是同步阻塞IO流 每个获取的连接都需要创建一个新的处理线程

LBPH模型 java javaio模型_操作系统

服务器端:

package cn.et;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 同步阻塞模型BIO
 * 使用byte[]数组缓冲数据
 * @author jiaozi
 *
 */
public class BIOServer {

	private static ServerSocket serverSocket=null;
	private static int bufferSize=4096;
	private static byte[] readBuffer=new byte[bufferSize];

	public static void main(String[] args) throws IOException {
		serverSocket = new ServerSocket(8899);
		//这里表示同步 必須等待接受到一个Socket才能执行后面的代码
		while(true) {
			//獲取一個客戶端連接
			Socket accept = serverSocket.accept();
			//必須為一個客戶端連接創建一個處理線程
			DThread th=new DThread(accept);
			th.start();
		}
		
	}
	/**
	 * 處理每個連接的線程 主要讀和寫操作
	 * @author jiaozi
	 */
	static class DThread extends Thread{
		Socket accept;
		public DThread(Socket accept) {
			this.accept = accept;
		}

		public void run() {
			//獲取輸入流
			try {
				InputStream inputStream = this.accept.getInputStream();
				OutputStream outputStream=this.accept.getOutputStream();
				//沒有數據讀到 會阻塞直到有數據
				int readCount = inputStream.read(readBuffer);
				String readText=new String(readBuffer,0,readCount);
				System.out.println("讀取的數據為:"+readText);
				outputStream.write("hello client".getBytes());
				outputStream.flush();
				accept.close();
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

客户端:

package cn.et;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
/**
 * BIO的客戶端
 * @author jiaozi
 */
public class BIOClient {
	private static int port=8899;
	private static int bufferSize=4096;
	private static byte[] readBuffer=new byte[bufferSize];
	public static void main(String[] args) throws UnknownHostException, IOException {
		Socket socket=new Socket("localhost",port);
		OutputStream outputStream = socket.getOutputStream();
		InputStream inputStream = socket.getInputStream();
		outputStream.write("hello server".getBytes());
		outputStream.flush();
		int readCount = inputStream.read(readBuffer);
		String readText=new String(readBuffer,0,readCount);
		System.out.println("讀取的數據為:"+readText);
	}
}

演示 结果 启动BIOServer 发现BIOServer处于同步阻塞状态  启动客户端 


服务端接受到消息 :讀取的數據為:hello server
客户端接受到消息  : 讀取的數據為:hello client


2.NIO编程实现

Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。


下面是java NIO的工作原理:


 由一个专门的线程来处理所有的 IO 事件,并负责分发。


 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。


 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。

NIO的原理

服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止

LBPH模型 java javaio模型_java_02

核心对象
Buffer和Channel是标准NIO中的核心对象
   Buffer:是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行,即 Channel 是通过 Buffer 来读写数据的。
使用 Buffer 读写数据一般遵循以下四个步骤:
写入数据到 Buffer;
调用 flip() 方法;
从 Buffer 中读取数据;
调用 clear() 方法或者 compact() 方法。
   Channel:是一个对象,可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:
Channel是双向的,既可以读又可以写,而流是单向的
Channel可以进行异步的读写
对Channel的读写必须通过buffer对象
在Java NIO中Channel主要有如下几种类型:
 FileChannel:从文件读取数据的
 DatagramChannel:读写UDP网络协议数据
 SocketChannel:读写TCP网络协议数据
 ServerSocketChannel:可以监听TCP连接

服务器端:

package cn.et;

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;
import java.util.Set;

/**
 * NIO 同步非阻塞 該種方式 阻塞多路複用器 Selector 使用轮询方式查找是否存在io事件
 *   IO事件分为  OP_WRITE[缓冲区可以写入],OP_READ【缓存区有数据可读】,OP_CONNECT【客户端连接】,OP_ACCEPT[服务端接受到一个连接]
 * @author jiaozi
 */
public class NIOServer {
	
	private static ServerSocketChannel serverSocketChannel=null;
	private static int bufferSize=4096;
	private static ByteBuffer readBuffer=ByteBuffer.allocate(bufferSize);
	private static ByteBuffer writeBuffer=ByteBuffer.allocate(bufferSize);
	private static Selector selector;
	
	public static void main(String[] args) throws IOException {
		//打开通道
		serverSocketChannel=ServerSocketChannel.open();
		//设置为非阻塞
		serverSocketChannel.configureBlocking(false);
		serverSocketChannel.bind(new InetSocketAddress(8899));
		selector=Selector.open();
		//通道注册到多路复用器 selector中 selector监听事件 接受客户端请求事件
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		listen();
	}
	public static void listen() throws IOException {
		//存在注册的事件返回时 方法返回 否则一直阻塞 应该轮询
		while(true) {
			selector.select();
			//select到了后面 说明存在事件 获取事件 并轮询
			Set<SelectionKey> selectedKeys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = selectedKeys.iterator();
			while(iterator.hasNext()) {
				SelectionKey selectionKey = iterator.next();
				iterator.remove();
				//有客户端连接事件
				if(selectionKey.isAcceptable()) {
					  ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();  
	                  // 获得和客户端连接的通道  
	                  SocketChannel channel = server.accept(); 
	                  //这里要配置一下 否则报错
	                  channel.configureBlocking(false);
	                  //给接入的客户端注册读的事件
	                  channel.register(selector, SelectionKey.OP_READ);
	                  writeBuffer.put("hello client".getBytes());
	                  writeBuffer.flip();
	                  //写入测试数据
	                  channel.write(writeBuffer);
				}
				//缓冲区有数据可读事件
				if(selectionKey.isReadable()) {
					SocketChannel channel = (SocketChannel) selectionKey.channel();  
					int readCount=channel.read(readBuffer);
					String readText=new String(readBuffer.array(),0,readCount);
					System.out.println("讀取的數據為:"+readText);
					readBuffer.clear();
				}
			}
		}
	}
}

客户端:

package cn.et;

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 static SocketChannel socketChannel=null;
	private static int bufferSize=4096;
	private static ByteBuffer readBuffer=ByteBuffer.allocate(bufferSize);
	private static Selector selector;
	private static int port=8899;
	public static void main(String[] args) throws IOException {
		socketChannel=SocketChannel.open();
		selector=Selector.open();
		socketChannel.configureBlocking(false);
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
		socketChannel.connect(new InetSocketAddress(port));
		connect();
	}
	public static void connect() throws IOException {
		// 轮询访问selector    
        while (true) {    
            // 选择一组可以进行I/O操作的事件,放在selector中,客户端的该方法不会阻塞,    
            //这里和服务端的方法不一样,查看api注释可以知道,当至少一个通道被选中时,    
            //selector的wakeup方法被调用,方法返回,而对于客户端来说,通道一直是被选中的    
            selector.select();    
            // 获得selector中选中的项的迭代器    
            Iterator<SelectionKey> ite = selector.selectedKeys().iterator();    
            while (ite.hasNext()) { 
            	SelectionKey selectionKey = ite.next();
            	ite.remove();
            	//如果是连接事件
            	if(selectionKey.isConnectable()) {
            		SocketChannel channel = (SocketChannel) selectionKey.channel();    
            		//如果正在连接 等待完成连接
            		if(channel.isConnectionPending()) {
            			channel.finishConnect();
            		}
            		channel.write(ByteBuffer.wrap(new String("hello server").getBytes()));    
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。    
                    channel.register(selector, SelectionKey.OP_READ);   
            	}
            	if(selectionKey.isReadable()) {
            		SocketChannel channel = (SocketChannel) selectionKey.channel();  
					int readCount=channel.read(readBuffer);
					String readText=new String(readBuffer.array(),0,readCount);
					System.out.println("讀取的數據為:"+readText);
					readBuffer.clear();
            	}
            }
        }
	}

}


3.AIO编程实现

aio是实现了真正的异步非阻塞 将所有的io将给操作系统处理 当io事件触发时 回调用户的Handler即可 不再有nio1的轮询 linux下epol也是基于aio  aio是在jdk1.7开始实现的

服务端:

package cn.et;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AIOServer {
	private static AsynchronousServerSocketChannel serverSocketChannel=null;
	private static int bufferSize=4096;
	private static ByteBuffer readBuffer=ByteBuffer.allocate(bufferSize);
	private static ByteBuffer writeBuffer=ByteBuffer.allocate(bufferSize);
	
	public static void main(String[] args) throws IOException {
		serverSocketChannel=AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8899));
		serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>(){

			@Override
			public void completed(AsynchronousSocketChannel channel, Object attachment) {
				try {
					
					//读的异步
					channel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {

						@Override
						public void completed(Integer result, Object attachment) {
							String readText=new String(readBuffer.array(),0,result);
							System.out.println("讀取的數據為:"+readText);
							readBuffer.clear();
						}

						@Override
						public void failed(Throwable exc, Object attachment) {
							exc.printStackTrace();
							System.out.println("读取失败");
						}
						
					});
					writeBuffer.put("hello client".getBytes());
	                writeBuffer.flip();
	                //写入测试数据
	                channel.write(writeBuffer);
	                serverSocketChannel.accept(null, this);// 监听新的请求,递归调用。
				} catch (Exception e) {
					e.printStackTrace();
				} 
			}

			@Override
			public void failed(Throwable exc, Object attachment) {
				System.out.println("出现异常:"+exc);
				serverSocketChannel.accept(null, this);// 监听新的请求,递归调用。
			}
			
		});
		
				
		System.in.read();
	}

}

客户端:


package cn.et;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.Selector;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AIOClient {
	private static AsynchronousSocketChannel socketChannel=null;
	private static int bufferSize=4096;
	private static ByteBuffer readBuffer=ByteBuffer.allocate(bufferSize);
	private static ByteBuffer writeBuffer=ByteBuffer.allocate(bufferSize);
	private static Selector selector;
	private static int port=8899;
	public static void main(String[] args) throws Exception {
		socketChannel=AsynchronousSocketChannel.open();
		//这里一定要指定ip 就算是本机  也要使用localhost
		socketChannel.connect(new InetSocketAddress("localhost",port),null,new CompletionHandler<Void, Object>() {

			@Override
			public void completed(Void result, Object attachment) {
				try {
					//读的异步
					socketChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
                   
						@Override
						public void completed(Integer result, Object attachment) {
							String readText=new String(readBuffer.array(),0,result);
							System.out.println("讀取的數據為:"+readText);
						}

						@Override
						public void failed(Throwable exc, Object attachment) {
							exc.printStackTrace();
							System.out.println("读取失败");
						}
						
					});
					//写入数据到服务端
					writeBuffer.put("hello server".getBytes());
					writeBuffer.flip();
					//写入测试数据
					socketChannel.write(writeBuffer);
					
				} catch (Exception e) {
					e.printStackTrace();
				} 
			}

			@Override
			public void failed(Throwable exc, Object attachment) {
				// TODO Auto-generated method stub
				
			}
		});
		
		
		System.in.read();
        
	}
}