一、前沿
在通信框架中经常使用到的三种通信模式,即 BIO、NIO 和 AIO,它们也是面试中经常被问到的,如果学会了它们将会给你带来薪资的变化哦。下面分别对三者介绍一下,通过示例理解其用法
下面先通过一张图来简单了解一下三者,如下所示:
同步阻塞IO : 用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行
同步非阻塞IO: 用户进程发起一个IO操作以后,可做其它事情,但用户进程需要经常询问IO操作是否完成,这样造成不必要的CPU资源浪费
异步非阻塞IO: 用户进程发起一个IO操作然后,立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,类比Future模式
二、BIO
2.1 定义
BIO,全称 Block-IO, 是一种阻塞同步的通信模式,也就是我们常说的 Socket IO 一般指的就是 BIO
2.2 优点
模式简单,使用方便
2.3 缺点
并发处理能力低,通信耗时
2.4 原理
服务器通过一个Acceptor线程监听客户端请求并为每个请求创建一个线程来处理,一对一的处理方式(一个请求对应一个线程处理),这里为了解决频繁地创建和销毁线程带来资源的损耗,改进为使用线程池来为请求分配线程处理
2.5 小结
BIO模型中通过 Socket 和 ServerSocket 完成套接字通道的实现, 阻塞,同步,建立连接耗时
服务器提供IP地址和监听的端口,客户端使用服务器提供的IP地址和端口通过TCP三次握手与服务器连接,连接成功之后,通过 Socket 套接字通信
2.6 示例demo
BIO 服务器代码:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.springboot.demo.io.Constants;
/**
* BIO服务端
* <p>
* BIO模型中通过Socket和ServerSocket完成套接字通道的实现。阻塞,同步,建立连接耗时
* <p>
* IO 也称为 BIO,Block IO 阻塞同步的通讯方式
* 比较传统的技术,实际开发中基本上用Netty或者是AIO。熟悉BIO,NIO,体会其中变化的过程。作为一个web开发人员,stock通讯面试经常问题。
* BIO最大的问题是:阻塞,同步。
* BIO通讯方式很依赖于网络,若网速不好,阻塞时间会很长。每次请求都由程序执行并返回,这是同步的缺陷。
* BIO工作流程:
* 第一步:server端服务器启动
* 第二步:server端服务器阻塞监听client请求
* 第三步:server端服务器接收请求,创建线程实现任务
*
* @date 2019-12-03 14:18
*/
public class BIOServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
ThreadPoolExecutor threadPoolExecutor = null;
try {
// 启动服务监听
serverSocket = new ServerSocket(Constants.BIO_PORT);
System.out.println("BIO server starting.....");
/* while (true){
// 服务器监听:阻塞,等待Client请求
socket = serverSocket.accept();
System.out.println("server 服务器确认请求 : " + socket);
// 服务器连接确认:确认Client请求后,创建线程执行任务 。很明显的问题,若每接收一次请求就要创建一个线程,显然是不合理的。
new Thread(new BIOServerHandler(socket)).start();
}*/
threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1000,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
while (true) {
// 服务器监听:阻塞,等待Client请求
socket = serverSocket.accept();
threadPoolExecutor.execute(new BIOServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
socket.close();
}
if (serverSocket != null) {
serverSocket.close();
System.out.println("BIO Server closed !!!!");
}
threadPoolExecutor.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* BIO服务处理器
*
* @date 2019-12-03 14:25
**/
public class BIOServerHandler implements Runnable {
private Socket socket;
public BIOServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
printWriter = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
// 若客户端用的是 writer.print() 传值,那readerLine() 是不能获取值的
body = bufferedReader.readLine();
if (body == null) {
break;
}
System.out.println(Thread.currentThread().getName() + ", BIO server receive data:" + body);
// 返回客户端数据
printWriter.println(body + " data is received" );
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (printWriter != null) {
printWriter.close();
}
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (this.socket != null) {
this.socket.close();
this.socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO服务器主要实现了以下逻辑:
1)、服务启动,阻塞服务,端口监听客户端请求,为客户端请求分配线程处理
2)、从 Socket 获取请求数据,然后将返回数据写入,返回 Socket 套接字给客户端
BIO客户端代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import com.springboot.demo.io.Constants;
/**
* BIO客户端
*
* @date 2019-12-03 14:38
**/
public class BIOClient {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
clientRequest(i);
}
}
private static void clientRequest(int i){
Socket socket = null;
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
socket = new Socket(Constants.HOST, Constants.BIO_PORT);
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
printWriter = new PrintWriter(socket.getOutputStream(),true);
String data = "客户端" + i + "数据";
// 向服务端发送数据
printWriter.println(data);
System.out.println(i + " 客户端请求返回数据 : " + bufferedReader.readLine());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(socket != null){
socket.close();
}
if(bufferedReader != null){
bufferedReader.close();
}
if(printWriter != null){
printWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO客户端主要实现了以下逻辑:
1)、启动客户端,即通过服务器IP地址和端口和服务器建立 Socket 连接,发起请求
2)、从 Socket 套接字中获取请求返回的数据
三、NIO
3.1 定义
NIO 全称 New IO,也叫Non-Block IO, 是一种非阻塞同步的通信模式
3.2 优点
并发能力好,吞吐量大
3.3 缺点
模式相对复杂,使用起来复杂
3.4 原理
服务端与客户端通过 Channel 通信,NIO 在 Channel 上进行数据读写操作,而这些 Channel 都会被注册在 Selector 多路复用器上,Selector 通过一个线程不停地轮询这些已经注册的 Channel,筛选出已经准备好的 Channel 进行IO操作
3.5 小结
NIO模型中通过 SocketChannel 和 ServerSocketChannel 完成套接字通道的实现,非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销
NIO 通过一个线程轮询 Channel 达到处理很多客户端请求
3.6 名词解释
NIO 通过 Channel 通信,NIO的数据操作都是在缓冲区中进行的,使用 Selector 多路复用器轮询,下面分别介绍一下这些名词
Channel(通道): 和流不同,通道是双向的。NIO可以通过Channel进行数据的读,写和同时读写操作。通道分为两大类:一类是网络读写(SelectableChannel),一类是用于文件操作(FileChannel),我们使用的 SocketChannel 和 ServerSocketChannel 都是 SelectableChannel 的子类
Buffer(缓冲区): 它是NIO与BIO的一个重要区别,NIO的数据操作都是在缓冲区中进行的,而BIO是在Stream流中。缓冲区实际上是一个数组,最常见的Buffer类型是 ByteBuffer,另外还有CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer
Selector(多路复用器):NIO编程的基础,多路复用器提供选择已经就绪的任务的能力。就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。服务器端只要提供一个线程负责Selector的轮询,就可以接入成千上万个客户端,这就是JDK NIO库的巨大进步
3.7 示例demo
NIO 服务端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import com.springboot.demo.io.Constants;
/**
* NIO服务
* <p>
* NIO模型中通过SocketChannel和ServerSocketChannel完成套接字通道的实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销
* <p>
* NIO 也称 New IO, Non-Block IO,非阻塞同步通信方式
* 从BIO的阻塞到NIO的非阻塞,这是一大进步。功归于Buffer,Channel,Selector三个设计实现。
* Buffer : 缓冲区。NIO的数据操作都是在缓冲区中进行。缓冲区实际上是一个数组。而BIO是将数据直接写入或读取到Stream对象。
* Channel : 通道。NIO可以通过Channel进行数据的读,写和同时读写操作。
* Selector : 多路复用器。NIO编程的基础。多路复用器提供选择已经就绪状态任务的能力。
* 客户端和服务器通过Channel连接,而这些Channel都要注册在Selector。Selector通过一个线程不停的轮询这些Channel。找出已经准备就绪的Channel执行IO操作。
* NIO通过一个线程轮询,实现千万个客户端的请求,这就是非阻塞NIO的特点。
*
* @date 2019-12-03 15:11
**/
public class NIOServer {
public NIOServer() {
}
private Selector startNIOServer() {
try {
// 1、打开多路复用器
Selector selector = Selector.open();
// 2、打开服务器通道(网络读写通道)
ServerSocketChannel socketChannel = ServerSocketChannel.open();
// 3、设置服务器通道为非阻塞模式,true 为阻塞,false 为非阻塞
socketChannel.configureBlocking(false);
// 4、绑定端口
socketChannel.bind(new InetSocketAddress(Constants.NIO_PORT));
// 5、把通道注册到多路复用器上,并监听阻塞事件
/**
* SelectionKey.OP_READ : 表示关注读数据就绪事件
* SelectionKey.OP_WRITE : 表示关注写数据就绪事件
* SelectionKey.OP_CONNECT: 表示关注socket channel的连接完成事件
* SelectionKey.OP_ACCEPT : 表示关注server-socket channel的accept事件
*/
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO server starting......");
return selector;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Selector selector = new NIOServer().startNIOServer();
new Thread(new NIOServerHandler(selector)).start();
}
}
import java.io.IOException;
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 com.springboot.demo.io.Constants;
/**
* NIO服务处理器
*
* @date 2019-12-03 15:11
**/
public class NIOServerHandler implements Runnable {
// 多路复用器,NIO编程的基础,负责管理通道Channel
private Selector selector;
// 缓冲区buffer
private ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
public NIOServerHandler(Selector selector) {
this.selector = selector;
}
/**
* 开启线程负责Selector轮询
*/
@Override
public void run() {
while (true) {
try {
/**
* a.select() 阻塞到至少有一个通道在你注册的事件上就绪
* b.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut
* c.selectNow() 立即返回。如果没有就绪的通道则返回0
* select方法的返回值表示就绪通道的个数。
*/
// 1、多路复用器监听阻塞
selector.select();
System.out.println("阻塞了吗?");
// 2、获取多路复用器已经选择的结果集
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
// 3、不停地轮询所有的Channel
while (selectionKeys.hasNext()) {
// 4、获取当前选中的key
SelectionKey key = selectionKeys.next();
// 5、获取后便将其从容器中移除
selectionKeys.remove();
if (!key.isValid()) {
// 只获取有效地key
continue;
}
// 新来的请求
if (key.isAcceptable()) {
accept(key);
}
// 可读状态处理
if (key.isReadable()) {
System.out.println("开始读取数据");
read(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void read(SelectionKey key) {
try {
// 1、清空缓冲区数据
byteBuffer.clear();
// 2、获取在 selector 上注册的channel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 3、读取数据
int index = socketChannel.read(byteBuffer);
if (index == -1) {
// -1 标示无任何数据
socketChannel.close();
key.cancel();
return;
}
// 4、有数据则在读取数据前进行复位操作
byteBuffer.flip();
// 5、根据缓冲区大小创建一个相应大小的bytes数组,用来获取值
byte[] bytes = new byte[byteBuffer.remaining()];
// 6、接收缓冲区数据
byteBuffer.get(bytes);
String requestMsg = new String(bytes);
System.out.println("NIO server received data:" + requestMsg);
// 返回响应数据给客户端
write(socketChannel, requestMsg + " is response");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 返回数据给客户端
*
* @param socketChannel
* @param s
*/
private void write(SocketChannel socketChannel, String s) {
try {
// 创建ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
// 将返回数据放入缓存区
byteBuffer.put(s.getBytes());
// 缓存区数据复位
byteBuffer.flip();
// 发送缓冲区数据
socketChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
// 设置阻塞,等待Client请求。在传统IO编程中,用的是ServerSocket和Socket。在NIO中采用的ServerSocketChannel和SocketChannel
private void accept(SelectionKey key) {
try {
// 1、获取通道服务
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 2、执行阻塞方法
SocketChannel socketChannel = serverSocketChannel.accept();
// 3、设置通道服务为非阻塞,true 为阻塞,false 为非阻塞
socketChannel.configureBlocking(false);
// 通道注册到 selector 上去,并设置读取标识
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO服务端主要实现了以下逻辑:
1)、开启多路复用器 Selector,打开通道 ServerSocketChannel,通道绑定端口,注册通道
2)、Selector 轮询通道,处理请求事件,返回响应数据
NIO 客户端代码:
import java.util.Scanner;
/**
* NIO客户端
*
* @date 2019-12-03 16:10
**/
public class NIOClient {
private static NIOClientHandler nioClientHandler;
public static void startClient(){
nioClientHandler = new NIOClientHandler();
new Thread(nioClientHandler).start();
}
public static void main(String[] args) {
// 开启客户端
NIOClient.startClient();
// 发送消息
while(nioClientHandler.sendMsg(new Scanner(System.in).nextLine()));
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import com.springboot.demo.io.Constants;
/**
* NIO客户端处理器
*
* @date 2019-12-03 15:11
**/
public class NIOClientHandler implements Runnable {
// 多路复用器,NIO编程的基础,负责管理通道Channel
private Selector selector;
private SocketChannel socketChannel;
// 缓冲区buffer
private ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
public NIOClientHandler() {
try {
// 1、打开多路复用器
selector = Selector.open();
// 2、打开 SocketChannel 通道
socketChannel = SocketChannel.open();
// 3、设置为非阻塞模式,true 为阻塞,false 为非阻塞
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 开启线程负责Selector轮询
*/
@Override
public void run() {
doConnect();
while (true) {
try {
/**
* a.select() 阻塞到至少有一个通道在你注册的事件上就绪
* b.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut
* c.selectNow() 立即返回。如果没有就绪的通道则返回0
* select方法的返回值表示就绪通道的个数。
*/
// 1、多路复用器监听阻塞
selector.select();
// 2、获取多路复用器已经选择的结果集
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
// 3、不停地轮询所有的Channel
while (selectionKeys.hasNext()) {
// 4、获取当前选中的key
SelectionKey key = selectionKeys.next();
// 5、获取后便将其从容器中移除
selectionKeys.remove();
dealEvent(key);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void dealEvent(SelectionKey key) {
// 只获取有效地key
if (key.isValid()) {
// 连接事件
if (key.isConnectable()) {
System.out.println("连接事件来了");
connect(key);
}
// 可读状态处理
if (key.isReadable()) {
System.out.println("开始读取数据");
read(key);
}
}
}
private void connect(SelectionKey key) {
try {
SocketChannel socketChannel = (SocketChannel) key.channel();
if (!socketChannel.finishConnect()) {
System.out.println("finish connect?");
System.exit(1);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void doConnect() {
try {
System.out.println("连接了。。。。");
InetSocketAddress socketAddress = new InetSocketAddress(Constants.HOST,Constants.NIO_PORT);
if(!socketChannel.connect(socketAddress)){
System.out.println("connect is false?");
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(SelectionKey key) {
try {
// 1、清空缓冲区数据
byteBuffer.clear();
// 2、获取在 selector 上注册的channel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 3、读取数据
int index = socketChannel.read(byteBuffer);
if (index == -1) {
// -1 标示无任何数据
socketChannel.close();
key.cancel();
return;
}
// 4、有数据则在读取数据前进行复位操作
byteBuffer.flip();
// 5、根据缓冲区大小创建一个相应大小的bytes数组,用来获取值
byte[] bytes = new byte[byteBuffer.remaining()];
// 6、接收缓冲区数据
byteBuffer.get(bytes);
String requestMsg = new String(bytes);
System.out.println("NIO client received data:" + requestMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 返回数据给客户端
*
* @param content
*/
private void write(String content) {
try {
// 创建ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
// 将返回数据放入缓存区
byteBuffer.put(content.getBytes());
// 缓存区数据复位
byteBuffer.flip();
// 发送缓冲区数据
socketChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean sendMsg(String content){
if("stop".equals(content)){
return false;
}
try {
socketChannel.register(selector, SelectionKey.OP_READ);
write(content);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
return true;
}
}
NIO客户端主要实现了以下逻辑:
1)、开启多路复用器 Selector,打开通道 SocketChannel,注册通道,连接服务器
2)、发送请求,发送数据
3)、Selector 轮询通道,处理请求响应数据事件
四、AIO
4.1 定义
AIO 也叫NIO2.0 ,是一种非阻塞异步的通信模式,在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现
4.2 优点
相比NIO,并发能力更好,吞吐量更大
4.3 缺点
模式相对复杂,使用起来复杂
4.4 原理
AIO 并没有采用NIO的多路复用器 Selector,而是使用异步通道,其read,write方法的返回类型都是Future对象。而Future模型是异步的,其核心思想是:在主函数等待数据返回即可
4.5 小结
AIO模型中通过 AsynchronousSocketChannel 和 AsynchronousServerSocketChannel 完成套接字通道的实现,非阻塞,异步
4.6 示例demo
AIO 服务端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.springboot.demo.io.Constants;
/**
* AIO服务
* <p>
* AIO 也叫NIO2.0 是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
* AIO 并没有采用NIO的多路复用器,而是使用异步通道的概念。其read,write方法的返回类型都是Future对象,而Future模型是异步的,
* 其核心思想是:去主函数等待时间
* <p>
* 小结:AIO模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的实现。非阻塞,异步。
*
* @date 2019-12-03 17:03
**/
public class AIOServer {
// 线程池
private ExecutorService executorService;
// 通道组
private AsynchronousChannelGroup channelGroup;
// 服务器通道
public AsynchronousServerSocketChannel serverSocketChannel;
public AIOServer() {
}
public void startAIOServer() {
try {
// 1、创建一个缓存线程池
executorService = Executors.newCachedThreadPool();
// 2、创建通道组
channelGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
// 3、创建服务器通道
serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
// 4、绑定网络
serverSocketChannel.bind(new InetSocketAddress(Constants.AIO_PORT));
System.out.println("AIO server starting......");
// 等待客户端请求
serverSocketChannel.accept(this, new AIOServerHandler());
// 一直阻塞 不让服务器停止,真实环境是在tomcat下运行,所以不需要这行代码
TimeUnit.MILLISECONDS.sleep(Integer.MAX_VALUE);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new AIOServer().startAIOServer();
}
}
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import com.springboot.demo.io.Constants;
/**
* AIO服务处理器
*
* @date 2019-12-03 17:17
**/
public class AIOServerHandler implements CompletionHandler<AsynchronousSocketChannel,AIOServer> {
@Override
public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
// 保证多个客户端都可以阻塞
attachment.serverSocketChannel.accept(attachment,this);
read(result);
}
// 读取数据
private void read(final AsynchronousSocketChannel socketChannel) {
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
socketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 读取之后重置标记位
attachment.flip();
// 获取读取的数据
String resultData = new String(attachment.array()).trim();
System.out.println("AIO server received data:" + resultData);
//
String responseContent = resultData + " response";
write(socketChannel, responseContent);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
private void write(AsynchronousSocketChannel socketChannel, String responseContent) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
// 数据放入缓冲区中
byteBuffer.put(responseContent.getBytes());
byteBuffer.flip();
// 缓冲区中数据写入到通道中
socketChannel.write(byteBuffer).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, AIOServer attachment) {
exc.printStackTrace();
}
}
AIO服务端主要实现了以下逻辑:
1)、创建服务器通道 AsynchronousServerSocketChannel,绑定端口,等待客户端请求
2)、读取请求数据,处理数据,写入返回数据
AIO 客户端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import com.springboot.demo.io.Constants;
/**
* AIO客户端
*
* @date 2019-12-03 17:32
**/
public class AIOClient implements Runnable {
private AsynchronousSocketChannel socketChannel;
private Integer index;
public AIOClient(Integer index) {
try {
this.index = index;
// 打开通道
socketChannel = AsynchronousSocketChannel.open();
// 创建连接
socketChannel.connect(new InetSocketAddress(Constants.HOST,Constants.AIO_PORT));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String content = index + " -> 客户端数据";
try {
socketChannel.write(ByteBuffer.wrap(content.getBytes())).get();
ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
while (true){
int count = socketChannel.read(byteBuffer).get();
byteBuffer.flip();
if(count == -1){
continue;
}
byte[] responseBytes = new byte[byteBuffer.remaining()];
// 将缓冲区的数据放入到 byte数组中
byteBuffer.get(responseBytes);
System.out.println(index+" 客户端 receive response :" + new String(responseBytes).trim());
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
AIOClient aioClient = new AIOClient(i);
new Thread(aioClient,"t-"+i).start();
}
}
}
AIO客户端主要实现了以下逻辑:
1)、打开通道 AsynchronousSocketChannel,连接服务器
2)、发送请求,读取请求响应返回数据
五、BIO、NIO 和 AIO 区别
BIO : IO 阻塞同步通信模式,客户端和服务器连接需要三次握手,使用简单,但吞吐量小
NIO : 非阻塞同步通信模式,客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel,提高吞吐量和可靠性
AIO : 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法
三者对比总结为表格如下:
BIO |
NIO | AIO | |
是否阻塞 | 阻塞 | 非阻塞 | 非阻塞 |
同步/异步 | 同步 | 同步 | 异步 |
性能 | 较低 | 较高 | 高 |
线程比(请求:线程) | 1:1 | N:1 | N:0 |
吞吐量 | 低 | 高 | 更高 |
六、总结
BIO模型中通过 Socket 和 ServerSocket 完成套接字通道实现。阻塞,同步,连接耗时
NIO模型中通过 SocketChannel 和 ServerSocketChannel 完成套接字通道实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销
AIO模型中通过 AsynchronousSocketChannel 和 AsynchronousServerSocketChannel 完成套接字通道实现。非阻塞,异步