深入浅析BIO、NIO、AIO
https://www.cnblogs.com/henuliulei/p/15143649.html
BIO、NIO、AIO
Java的I/O演进之路I/O模型
:就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能,Java共支持3种网络编程的I/O模型:BIO、NIO、AIO
实际 通行需求下,要根据不同的业务场景和性能需求决定选择不同的I/O模型
1.I/O模型
BIO
同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销
NIO
同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
AIO
(又称为NIO 2.0)异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,一般适用于连接数较多且连接时间较长的应用
异步非阻塞:为什么说AIO是异步非阻塞?通过AIO发起个文件IO操作之后,你立马就返回可以干别的事儿了,接下来你也不用管了,操作系统自己干完了IO之后,告诉你说ok了, 当你基于AIO的api去读写文件的时候, 当你发起一个请求之后,剩下的事情就是交给了操作系统,当读写完成后, 操作系统会来回调你的接口, 告诉你操作完成, 在这期间不需要等待, 也不需要去轮询判断操作系统完成的状态,你可以去干其他的事情。 同步就是自己还得主动去轮询操作系统,异步就是操作系统反过来通知你。所以来说, AIO就是异步非阻塞的。
2.适用场景分析
1、BlO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
2、NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。
3、AlO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS(OutputStream)参与并发操作,编程比较复杂,JDK7开始支持。
BIO 深入剖析
1.简介
Java BIO就是传统的java io 编程,其相关的类和接口在java.io
BlO(blocking l/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).
2.BIO工作模式
3.实例分析
单发机制
但发单收
Server
package bio;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
//对服务端端口进行注册 9999
ServerSocket serverSocket = new ServerSocket(9999);
//监听客户端socket请求
Socket socket = serverSocket.accept();
//从socket管道中得到一个字节输入流对象
InputStream is = socket.getInputStream();
//将字节输入流包装成一个缓冲字符输入流,提高效率
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null){
System.out.println("接收到客户端消息:"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client
package bio;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
//创建socket链接
Socket socket = new Socket("127.0.0.1",9999);
//从socket对象中获取一个输出流
OutputStream out = socket.getOutputStream();
//把字节输出流包装成打印流
PrintStream printStream = new PrintStream(out);
printStream.println("hello");
printStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
- 在以上通信中,服务端会一致等待客户端的消息,如果客户端没有进行消息的发送江服务端将一直进入阻塞状态。
- 同时服务端是按照行获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进入等待消息的阻塞状态!
- 可以考虑改为if判断
- 由于这种机制是端到端的,若连接的Client Socket断了,服务端的Socket也会出现异常机制。
多发和多收机制
在上一个demo的基础上作出一些修改,使得Client可以持续输入,然后后Server可以持续输出。保持两端之间通道的连接。
Server
package bio.persistent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
System.out.println("已开启Socket,等待客户端连接中");
//对服务端端口进行注册 9999
ServerSocket serverSocket = new ServerSocket(9999);
//监听客户端socket请求
Socket socket = serverSocket.accept();
System.out.println("匹配客户端成功");
//从socket管道中得到一个字节输入流对象
InputStream is = socket.getInputStream();
//将字节输入流包装成一个缓冲字符输入流,提高效率
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null){
System.out.println("接收到客户端消息:"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client
package bio.persistent;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
//创建socket链接
Socket socket = new Socket("127.0.0.1",9999);
//从socket对象中获取一个输出流
OutputStream out = socket.getOutputStream();
//把字节输出流包装成打印流
PrintStream printStream = new PrintStream(out);
Scanner scanner = new Scanner(System.in);
while (true){
String msg = scanner.nextLine();
printStream.println(msg);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
接收多个客户端
在上述的案例中,一个服务端只能接收一个客户端的通信请求,那么如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型,图解模式如下:
目标
实现服务端可以同时接收多个客户端的Socket通信需求。
思路
服务端每接收到一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求
代码
Server
package bio.Multi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
//注册 端口
ServerSocket serverSocket = new ServerSocket(9999);
//定义一个死循环,负责不断的去接受Client的Socket请求
while (true){
Socket socket = serverSocket.accept();
new Thread(()->{
try {
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String msg;
while ((msg = br.readLine()) != null){
System.out.println(Thread.currentThread().getName()+"接收到消息---->"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client
这里每启动一个Client,Server都会启动一个新的Thread去处理Client的请求
package bio.Multi;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1",9999);
PrintStream ps = new PrintStream(socket.getOutputStream());
Scanner sc = new Scanner(System.in);
while (true){
System.out.print("说点啥吧:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
- 每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能
- 每个线程都会占用栈空间和CPU资源;
- 并不是每个socket都进行IO操作,无意义的线程处理(等待)
- 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
4.伪异步I/O编程
概述
在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
接下来我们采用一个伪异步I/O的通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
目标
开发实现伪异步通行框架
代码
Server
package bio.pseasyn;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static void main(String[] args) {
try {
//对服务端端口进行注册 9999
ServerSocket serverSocket = new ServerSocket(9999);
/**
* 手动创建线程池
* 核心线程 3 ,最大 4,等待释放5s,任务队列4
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,4,5, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
while (true){
//监听客户端socket请求
Socket socket = serverSocket.accept();
//线程池执行
threadPoolExecutor.execute(new Thread(()->{
try {
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String msg;
while ((msg =