网络编程基础:从BIO到NIO再到AIO
前言
为什么要学习 BIO、NIO、AIO?
在学习 Netty 之前,先理解 Java 的三种 IO 模型:
- BIO:最基础的网络编程模型,理解网络通信原理
- NIO:Netty 的底层实现,理解高性能网络编程
- AIO:了解异步编程思想,拓展知识面
BIO、NIO、AIO 核心区别总结
总结
| 模型 | 核心特点 | 通俗比喻 |
|---|---|---|
| BIO | 同步阻塞:调用后等待结果,线程被阻塞 | 排队办业务,轮到你之前一直等 |
| NIO | 同步非阻塞:调用后立即返回,需要轮询检查 | 银行大堂经理,不断巡视哪个窗口有人 |
| AIO | 异步非阻塞:调用后立即返回,结果通过回调通知 | 外卖送达通知,到了自动打电话 |
BIO核心概念讲解
一、BIO 的三个角色
┌─────────────┐ ┌─────────────┐
│ Server │ │ Client │
│ (服务端) │◀────── Socket ─────│ (客户端) │
└─────────────┘ └─────────────┘
角色1 通信工具 角色2
1. Server(服务端)
- 作用:等待客户端连接,处理客户端请求
- 比喻:像餐厅,等待客人上门
- 代码:
MultiBioServer.java
2. Client(客户端)
- 作用:主动连接服务端,发送请求
- 比喻:像客人,主动去餐厅
- 代码:
SimpleBioClient.java
3. Socket(通信工具)
- 作用,是通信工具
- 作用:连接客户端和服务端,传输数据
- 比喻:像电话线,连接两个人
二、BIO 是什么?
BIO = Blocking IO(阻塞式输入输出)
核心特点:
// 1. 阻塞等待连接
Socket socket = serverSocket.accept(); // 没客户端连接,就一直等(阻塞)
// 2. 阻塞读取数据
String message = reader.readLine(); // 没数据,就一直等(阻塞)
BIO 的作用:
- 实现网络通信:让两台电脑能互相发消息
- 简单易用:代码直观,适合入门学习
- 适合低并发:客户端少的场景(比如内部系统)
三、Socket 详解
Socket 是什么?
Socket = 网络通信的端点
客户端 Socket 服务端 Socket
┌─────────────┐ ┌─────────────┐
│ InputStream │◀───── 数据 ─────│OutputStream │
│ │ │ │
│OutputStream │────── 数据 ────▶│ InputStream │
└─────────────┘ └─────────────┘
Socket 的两种类型:
| 类型 | 作用 | 使用场景 |
|---|---|---|
| ServerSocket | 监听端口,接受连接 | 服务端专用 |
| Socket | 实际的通信通道 | 客户端和服务端都用 |
四、关键 API 说明
| API | 作用 | 是否阻塞 |
|---|---|---|
ServerSocket.accept() | 等待客户端连接 | 是 |
BufferedReader.readLine() | 读取一行数据 | 是 |
PrintWriter.println() | 发送一行数据 | 否 |
Socket.getInputStream() | 获取输入流 | 否 |
Socket.getOutputStream() | 获取输出流 | 否 |
五、BIO案例代码
/**
* 最简单的 BIO 服务端
* BIO 核心特点:阻塞式 IO
* - accept() 会阻塞,等待客户端连接
* - readLine() 会阻塞,等待客户端发消息
*/
public class MultiBioServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7397);
System.out.println("服务端启动,等待客户端连接...");
// 循环接受客户端连接
while (true) {
// 等待客户端连接(阻塞)
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端连接:" + clientSocket.getRemoteSocketAddress());
// 为每个客户端创建一个线程处理
new Thread(new ClientHandler(clientSocket)).start();
}
}
/**
* 客户端处理器(每个客户端一个线程)
*/
static class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 获取输入输出流
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8")
);
PrintWriter writer = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),
true
);
// 发送欢迎消息
writer.println("欢迎连接到 BIO 服务器!");
// 循环读取客户端消息
String message;
while ((message = reader.readLine()) != null) {
System.out.println("[" + socket.getRemoteSocketAddress() + "] 说:" + message);
// 回复客户端
writer.println("服务端已收到:" + message);
// 如果客户端发送 "bye",结束连接
if ("bye".equalsIgnoreCase(message)) {
System.out.println("[" + socket.getRemoteSocketAddress() + "] 断开连接");
break;
}
}
// 关闭资源
reader.close(); writer.close(); socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 最简单的 BIO 客户端
*
* 功能:连接服务端,发送消息,接收回复
*/
public class SimpleBioClient {
public static void main(String[] args) throws IOException {
// 1. 连接服务端(IP + 端口)
Socket socket = new Socket("127.0.0.1", 7397);
System.out.println("已连接到服务端:" + socket.getRemoteSocketAddress());
// 2. 获取输入流(读取服务端消息)
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8")
);
// 3. 获取输出流(发送消息给服务端)
PrintWriter writer = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),
true // true 表示自动 flush
);
// 4. 读取服务端的欢迎消息
String welcome = reader.readLine();
System.out.println("服务端说:" + welcome);
// 5. 从控制台读取用户输入,发送给服务端
Scanner scanner = new Scanner(System.in);
System.out.println("请输入消息(输入 bye 退出):");
while (true) {
// 读取用户输入
String input = scanner.nextLine();
// 发送给服务端
writer.println(input);
// 读取服务端回复
String response = reader.readLine();
System.out.println("服务端回复:" + response);
// 如果输入 bye,退出
if ("bye".equalsIgnoreCase(input)) {
break;
}
}
// 6. 关闭资源
scanner.close(); reader.close();writer.close(); socket.close();
System.out.println("已断开连接");
}
}