一。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流 每个获取的连接都需要创建一个新的处理线程
服务器端:
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 client2.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时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止
核心对象
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(); } }