概念
Blocking IO也称为BIO,即同步阻塞IO。Java的io包基于流模型实现,提供了File、FileInputStream、FileOutputStream等输⼊输出流的功能。Java的io包下提供的流操作,交互⽅式是同步且阻塞的⽅式,在输⼊输出流的操作进⾏读、写动作完成之前,线程会⼀直阻塞。因此io包中对流的操作容易造成性能的瓶颈。同样的,在java.net包下提供的部分⽹络API,如Socket、ServerSocket、HttpURLConnection等,进⾏⽹络通信时,也⽤到了java.io包下的流操作,因此也属于同步且阻塞的IO⾏为。
实现逻辑
在BIO同步阻塞模型下,⼀个服务端的可以开启多个处理线程来处理客户端的连接,但是⼀个处理线程只能对应⼀个客户端的连接。
-
客户端的实现
package com.my.io.bio; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; /** * @author zhupanlin * @version 1.0 * @description: 客户端1 * @date 2024/1/24 10:41 */ public class SocketClient { public static void main(String[] args) throws IOException { // 1. 连接服务端 Socket socket = new Socket("localhost", 9090); // 2. 发送数据 OutputStream os = socket.getOutputStream(); os.write("hello bio from client 1".getBytes()); os.flush(); // 3.接收服务端返回的数据 InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println("client 1 接收到服务端返回的数据:" + new String(bytes, 0, len)); // 关闭连接 } }
-
单线程服务端的实现
package com.my.io.bio; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * @author zhupanlin * @version 1.0 * @description: 单线程的服务器 * @date 2024/1/24 10:26 */ public class SocketServerSingleThread { public static void main(String[] args) throws IOException { // 1.创建服务端的Socket ServerSocket serverSocket = new ServerSocket(9090); while (true){ System.out.println("等待客户端的连接"); // 阻塞等待客户端的连接 Socket socket = serverSocket.accept(); System.out.println("已有客户端连接了"); // 开始处理客户端的读写请求 InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; // 阻塞的等待客户端向io流通道中写数据 int len = is.read(bytes); System.out.println("收到客户端的数据: " + new String(bytes, 0, len)); // 服务端返回信息给客户端 OutputStream os = socket.getOutputStream(); os.write("success".getBytes()); os.flush(); } } }
-
多线程服务端的实现
为了解决单线程服务端的同步阻塞问题,BIO模型下可以让服务端使⽤多线程来同时处理多个客户端的请求。
package com.my.io.bio; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * @author zhupanlin * @version 1.0 * @description: 多线程服务端 * @date 2024/1/24 12:38 */ public class SocketServerMultipleThread { public static void main(String[] args) throws IOException { // 1.创建服务端的Socket ServerSocket serverSocket = new ServerSocket(9090); while (true){ System.out.println("等待客户端的连接"); // 阻塞等待客户端的连接 Socket socket = serverSocket.accept(); System.out.println("已有客户端连接"); new Thread(new Runnable() { @Override public void run() { try { handle(socket); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } } private static void handle(Socket socket) throws IOException { // 开始处理客户端的读写请求 InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; // 阻塞的等待客户端向io流通道中写数据 int len = is.read(bytes); System.out.println("收到客户端的数据:" + new String(bytes, 0, len)); // 服务端返回信息给客户端 OutputStream os = socket.getOutputStream(); os.write("success".getBytes()); os.flush(); } }
BIO的局限
在上⾯的例⼦中可以看出,IO代码⾥Read操作是阻塞操作,如果连接不做数据读写操作,会导致线程阻塞,浪费线程资源。
除此之外,如果客户端请求过多,将会导致服务端不断创建出新的线程,对服务器造成过⼤压⼒。
总结:
1 每个socket接收到,都会创建一个线程,线程的竞争,切换上下文影响性能。
2 每个线程都会占用栈空间和CPU资源
3 并不是每个socket都进行IO操作,无意义的线程处理;
4 客户端的并发访问量增加时,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出;线程创建失败,最终导致进程宕机或僵死,从而不能对外提供服务。
BIO的应用场景
BIO⽅式的使用和维护相对比较简单,适⽤于较小且固定的应用框架。