BIO(Blocking I/O)是 Java 最传统的 I/O 模型,也称为 同步阻塞 I/O。它的核心特点是:
BIO 的典型应用场景是 客户端-服务器(C/S)架构,尤其适用于连接数较少、负载较低的场景。
BIO 的实现基于 Socket 编程,核心流程如下:
创建 ServerSocket
对象
服务器通过 ServerSocket
绑定指定端口并监听客户端连接请求。
ServerSocket serverSocket = new ServerSocket(8888); // 监听8888端口
调用 accept()
方法阻塞等待连接
Socket socket = serverSocket.accept(); // 阻塞直到有客户端连接
accept()
是阻塞方法,线程会一直等待,直到有客户端发起连接请求。accept()
调用操作系统内核的 accept()
系统调用,内核在接收到连接请求后返回一个新的 Socket
对象。客户端创建 Socket
对象
客户端通过 Socket
主动连接服务器的 IP 和端口。
Socket clientSocket = new Socket("localhost", 8888); // 连接本地8888端口
Socket
调用操作系统内核的 connect()
系统调用,向服务器发起 TCP 三次握手。服务器接受连接
服务器的 accept()
方法返回后,会创建一个新的 Socket
对象,用于与客户端进行通信。
Socket
对象,服务器需要为每个连接分配一个线程处理。获取输入输出流
服务器和客户端通过 Socket
获取输入流(InputStream
)和输出流(OutputStream
)。
// 服务器端
InputStream inputStream = socket.getInputStream(); // 读取客户端数据
OutputStream outputStream = socket.getOutputStream(); // 向客户端发送数据
阻塞式读写操作
读数据(read()
)
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer); // 阻塞直到有数据可读
buffer
中,length
表示实际读取的字节数。写数据(write()
)
String message = "Hello, client!";
outputStream.write(message.getBytes()); // 阻塞直到数据发送完成
write()
会阻塞直到数据成功发送。主动关闭连接
通信完成后,客户端或服务器调用 close()
方法关闭连接。
socket.close(); // 关闭 Socket
serverSocket.close(); // 关闭 ServerSocket
Socket
会释放底层文件描述符(fd)、内存缓冲区等资源。清理资源
ServerSocket
以释放监听端口。Socket
以终止与服务器的连接。BIO 的底层依赖于 操作系统内核的阻塞式 I/O 模型,其核心原理如下:
socket()
:创建套接字(Socket),分配文件描述符(fd)。bind()
:将套接字绑定到本地地址和端口。listen()
:设置套接字为监听状态,等待连接请求。accept()
:阻塞等待客户端连接,返回新的套接字(用于通信)。read()
/write()
:阻塞式读写操作,直到数据准备好或写入完成。read()
时,如果内核缓冲区 没有数据,线程会被挂起(进入阻塞状态),直到数据到达。BIO服务端代码(BIOServer.java)
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class BIOServer {
public static void main(String[] args) {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("BIO Server started on port " + port);
// 循环等待客户端连接
while (true) {
// accept() 方法会阻塞,直到有客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getRemoteAddress());
// 为每个客户端连接创建一个线程处理
new Thread(new ClientHandler(clientSocket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 客户端处理线程
static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (
// 获取输入输出流
InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output))
) {
// 读取客户端发送的消息
String message;
while ((message = reader.readLine()) != null) {
System.out.println("Received from client: " + message);
// 向客户端发送响应
String response = "Server received: " + message;
writer.write(response);
writer.newLine(); // 写入换行符,确保客户端能正确读取
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
BIO客户端代码(BIOClient.java)
import java.io.*;
import java.net.Socket;
public class BIOClient {
public static void main(String[] args) {
String host = "localhost";
int port = 8080;
try (
// 创建 Socket 连接服务端
Socket socket = new Socket(host, port);
// 获取输入输出流
InputStream input = socket.getInputStream();
OutputStream output = socket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output))
) {
// 向服务端发送消息
String message = "Hello from BIO Client!";
writer.write(message);
writer.newLine(); // 写入换行符
writer.flush();
// 读取服务端的响应
String response = reader.readLine();
System.out.println("Server response: " + response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
优点:
缺点:
特性 | BIO(同步阻塞 I/O) | NIO(同步非阻塞 I/O) | AIO(异步非阻塞 I/O) |
---|---|---|---|
I/O 模型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
线程模型 | 一个连接一个线程 | 一个线程管理多个连接(多路复用) | 操作系统异步完成,无需线程阻塞 |
资源消耗 | 高(线程数 = 连接数) | 中(单线程管理多个连接) | 低(操作系统异步处理) |
性能瓶颈 | 线程数爆炸式增长 | 轮询开销 | 依赖操作系统支持 |
适用场景 | 低并发、小型应用 | 中高并发、轻量操作 | 高并发、重操作(如文件传输) |
尽管 BIO 的性能较低,单使用线程池限制最大线程数,避免无限制创建线程。
服务端代码(BIO + 线程池)
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServerWithThreadPool {
// 设置线程池核心线程数(可根据预期并发量调整)
private static final int THREAD_POOL_SIZE = 10;
public static void main(String[] args) throws IOException {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 创建 ServerSocket 监听端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已启动,监听端口 8888...");
try {
// 循环接收客户端连接
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端连接: " + clientSocket.getInetAddress());
// 将客户端连接任务提交给线程池处理
executorService.submit(() -> {
try (
InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream()
) {
// 读取客户端数据
byte[] buffer = new byte[1024];
int bytesRead;
StringBuilder request = new StringBuilder();
while ((bytesRead = input.read(buffer)) != -1) {
request.append(new String(buffer, 0, bytesRead));
if (request.toString().contains("\r\n")) break; // 假设以换行符结束
}
System.out.println("收到客户端消息: " + request);
// 向客户端发送响应
String response = "Hello from server!\r\n";
output.write(response.getBytes());
output.flush();
} catch (IOException e) {
System.err.println("处理客户端连接时发生异常: " + e.getMessage());
} finally {
try {
clientSocket.close();
System.out.println("客户端连接已关闭: " + clientSocket.getInetAddress());
} catch (IOException e) {
System.err.println("关闭客户端连接时发生异常: " + e.getMessage());
}
}
});
}
} finally {
// 关闭线程池和 ServerSocket
executorService.shutdown();
serverSocket.close();
System.out.println("服务器已关闭。");
}
}
}
BIO 是 Java 早期的 I/O 模型,其核心思想是 同步阻塞,通过线程池和系统调用实现网络通信。虽然在高并发场景下性能较差,但在 低并发、简单业务 的场景中仍有其价值。对于高性能需求,建议使用 NIO 或 AIO 替代。