一。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 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时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止
核心对象
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();
}
}