BIO (Blocking I/O)
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
这种IO方式即我们一开始学习java时接触到的IO方式。
传统 BIO
BIO通信(一请求一应答)
采用BIO来进行通信的模型,我们通常是在服务端设置一个独立的 Acceptor 线程负责监听客户端的连接。在while(true)
循环中服务端会调用 accept()
方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字.
在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接。学习java的时候,老师要求我们用网络编程开发一个聊天室,即通过这种方式。用多线程方式,服务端开启请求监听,一旦监听到一个连接即为其创建一个线程。
.如果要让 BIO 通信模型 能够同时处理多个客户端请求,就必须使用多线程(主要原因是socket.accept()
、socket.read()
、socket.write()
涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 一请求一应答通信模型 。如果这个连接不做任何事情的话就会造成不必要的线程开销。
我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?
在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux
这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。
伪异步 IO
BIO通信模型的优化,为了解决一个链路需要一个线程处理的问题。
伪异步 IO: 后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数N:线程池最大线程数M的比例关系,其中N可以远远大于M.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机,保证了系统资源有限的控制。并且使用线程池可以让线程的创建和回收成本相对较低
伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。
一请求一应答通信代码示例
(单用户通信程序)
Client端
package IO;
import jdk.net.Sockets;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* @create 2020-08-21 14:53
*/
public class BIOClient {
public static void main(String[] args) throws IOException {
Socket s = new Socket("127.0.0.1",12345);
DataOutputStream dataOut = new DataOutputStream(s.getOutputStream());
dataOut.writeUTF("Hello from client");
dataOut.close();
s.close();
}
}
Server端
package IO;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @create 2020-08-21 14:56
*/
public class BIOServer {
public static void main(String[] args){
ServerSocket server = null;
try {
server = new ServerSocket(12345);
} catch (IOException e) {
System.out.println("监听端口出错");
}
while(true){
try{
Socket s = server.accept();
int len ;
DataInputStream dataInt = new DataInputStream(s.getInputStream());
System.out.println(dataInt.readUTF());
dataInt.close();
s.close();
}catch (IOException e){
e.printStackTrace();
System.out.println("连接出错");
}
}
}
}