Java 网络编程核心:BIO、NIO、AIO IO 模型深度解析与实战
😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页 @怒放吧德德 To记录领地 @一个有梦有戏的人
🌝分享学习心得,欢迎指正,大家一起学习成长!
转发请携带作者信息 @怒放吧德德(掘金) @一个有梦有戏的人(CSDN)

前言
在分布式系统与高并发场景成为主流的今天,Java 网络编程作为后端开发的核心基础,其 IO 模型的选择直接决定了系统的性能上限。从早期的 BIO(同步阻塞 IO)到为解决高并发而生的 NIO(同步非阻塞 IO),再到更贴合异步编程理念的 AIO(异步非阻塞 IO),三种 IO 模型贯穿了 Java 网络编程的发展历程,也对应着不同的业务场景需求。
对于初学者而言,IO 模型的核心差异(阻塞 / 非阻塞、同步 / 异步)常常难以理解;而对于资深开发者,如何根据业务场景(如连接数、数据传输量、实时性要求)选择合适的 IO 模型,更是优化系统性能的关键。本文将从网络通信的基础概念(Socket)入手,层层拆解 BIO、NIO、AIO 的核心原理,结合可直接运行的实战案例,清晰对比三者的底层逻辑、适用场景与性能优劣,帮助读者不仅理解 "是什么",更能掌握 "怎么用",最终实现从理论到实践的落地,为构建高可用、高性能的 Java 网络应用打下坚实基础。
1 网络通信基本常识:Socket是什么?
1.1 Socket核心概念
Socket(套接字)是Java实现网络通信的核心组件,本质是一种"通信端点",用于在不同设备(或同一设备的不同进程)之间通过网络传输数据。它就像两个设备之间的"通信管道接口",一端是客户端Socket,一端是服务器端Socket,双方通过这个接口建立连接、发送和接收数据。
Java中Socket编程基于TCP/IP协议(主流),分为客户端Socket 和服务器端ServerSocket,核心流程遵循"三次握手建立连接→数据传输→四次挥手关闭连接"的TCP通信规范,无需开发者手动处理底层协议细节,Java已封装好相关API(位于java.net包、java.nio包下)。

1.2 Socket通信核心要素
- IP地址:标识网络中具体的设备(如192.168.1.1),相当于设备的"网络地址"。
- 端口号:标识设备上的具体进程(范围0-65535,0-1024为系统端口,建议使用1024以上端口),相当于"进程的门牌号",确保数据能准确送达目标程序。
- 通信协议:约定数据传输的格式和规则,Java网络编程主要用TCP(面向连接、可靠传输,适用于文件传输、登录验证等)和UDP(无连接、不可靠,适用于视频直播、聊天消息等实时场景),本文重点讲解TCP协议下的BIO、NIO、AIO。
1.3 Socket通信基本流程(TCP)
- 服务器端:创建ServerSocket,绑定指定端口,监听客户端的连接请求(阻塞/非阻塞,取决于IO模型)。
- 客户端:创建Socket,指定服务器的IP地址和端口,发起连接请求。
- 连接建立:服务器端接收客户端连接,生成对应的Socket(用于与该客户端通信),此时双方建立"双向通信通道"。
- 数据传输:客户端和服务器端通过Socket获取输入流(InputStream)和输出流(OutputStream),实现数据的读写。
- 关闭连接:通信结束后,关闭输入流、输出流和Socket,释放资源。
客户端和服务端在通信编程中,只关心三件事:连接、读数据、写数据。
2 Java IO模型概述
Java中网络编程的IO模型,本质是"服务器端处理客户端连接和数据读写的方式",主要分为三类:BIO(同步阻塞IO)、NIO(同步非阻塞IO)、AIO(异步非阻塞IO)。三者的核心区别在于"是否阻塞线程"和"是否主动等待IO操作完成",下面依次详解,每部分均搭配入门案例,确保可直接运行、易于理解。
2.1 BIO:同步阻塞 IO

2.1.1 BIO 核心原理
BIO(Blocking IO)即同步阻塞IO,是Java最早的IO模型,也是最易理解的模型。其核心特点是:一个客户端连接对应一个服务器端线程,线程在执行IO操作(等待连接、读取数据、写入数据)时会被阻塞,直到IO操作完成后,线程才能继续执行其他任务。
通俗来说,BIO就像"一个服务员对应一个顾客",服务员(线程)接待顾客(客户端)后,必须全程等待顾客点餐、用餐(IO操作),期间不能接待其他顾客,效率较低,但编程逻辑简单,适合入门学习。
BIO的核心缺陷:当客户端连接数量较多(如1000个)时,服务器端需要创建1000个线程,线程的创建和上下文切换会消耗大量系统资源,导致服务器性能急剧下降,甚至崩溃,因此BIO仅适用于连接数少、并发量低的场景(如本地测试、简单工具)。
2.1.2 BIO案例代码
案例说明:实现"客户端发送消息,服务器端接收消息并回复"的简单TCP通信,采用BIO模型,服务器端用多线程处理多个客户端连接(入门级多线程优化,避免单线程只能处理一个客户端)。
服务端:通过线程来处理
xml
/**
* Bio服务端 - 通过线程
* <p>
* 每次连接都创建一个线程,虽然能够解决,但是当链接特别多,就会导致创建很多的连接
* @author: lyd
* @date: 2026/1/29 22:46
*/
public class BioThreadServer {
public static void main(String[] args) {
// 1. 定义服务器端口(1024以上,避免占用系统端口)
int port = 8888;
ServerSocket serverSocket = null;
try {
// 2. 创建ServerSocket,绑定端口,开始监听客户端连接
serverSocket = new ServerSocket(port);
System.out.println("BIO服务器已启动,监听端口:" + port + ",等待客户端连接...");
// 3. 循环监听客户端连接(阻塞点1:serverSocket.accept(),等待连接时线程阻塞)
while (true) {
// 接收客户端连接,accept()方法会阻塞,直到有客户端发起连接
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接成功:" + clientSocket.getInetAddress().getHostAddress());
// 4. 为每个客户端创建一个独立线程,处理数据读写(避免单线程阻塞)
new Thread(() -> {
// 定义输入流(读取客户端消息)、输出流(回复客户端消息)
try (
InputStream is = clientSocket.getInputStream();
OutputStream os = clientSocket.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
PrintWriter pw = new PrintWriter(os, true)) { // autoFlush=true,自动刷新缓冲区
String clientMsg;
// 5. 读取客户端消息(阻塞点2:br.readLine(),无数据时线程阻塞)
while ((clientMsg = br.readLine()) != null) {
System.out.println("收到客户端[" + clientSocket.getInetAddress().getHostAddress() + "]消息:" + clientMsg);
// 6. 回复客户端消息 (底层也是调用了pw.write())
pw.println("服务器已收到消息:" + clientMsg);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
// 客户端断开连接时,关闭Socket
clientSocket.close();
System.out.println("客户端[" + clientSocket.getInetAddress().getHostAddress() + "]断开连接");
} catch (IOException ex) {
ex.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭ServerSocket,释放资源
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端
xml
/**
* @author: lyd
* @date: 2026/1/29 22:46
*/
public class BioClient {
public static void main(String[] args) {
// 1. 服务器IP(本地测试用127.0.0.1)和端口(与服务器一致)
String serverIp = "127.0.0.1";
int serverPort = 8888;
Socket socket = null;
try {
// 2. 创建Socket,连接服务器(阻塞点:连接未建立时,线程阻塞)
// *立即连接:在构造函数内部就完成了连接
socket = new Socket(serverIp, serverPort);
// SocketAddress add = new InetSocketAddress(serverIp, serverPort);
// 显示连接
// socket.connect(add);
System.out.println("连接服务器成功,可发送消息(输入exit退出):");
// 3. 获取输出流(发送消息)、输入流(接收服务器回复)
try (OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os, true); // 自动刷新
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
Scanner scanner = new Scanner(System.in)) {
String msg;
// 4. 循环输入消息,发送给服务器
while (true) {
msg = scanner.nextLine();
// 输入exit,退出客户端
if ("exit".equals(msg)) {
pw.println(msg); // 告知服务器客户端退出
break;
}
// 发送消息到服务器
pw.println(msg);
// 接收服务器回复(阻塞点:无回复时,线程阻塞)
String serverReply = br.readLine();
System.out.println("服务器回复:" + serverReply);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭Socket,释放资源
if (socket != null) {
try {
socket.close();
System.out.println("客户端已退出");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行说明
- 先运行BioServer类,控制台输出"BIO服务器已启动,监听端口:8888,等待客户端连接...",表示服务器启动成功。
- 再运行多个BioClient类(可同时运行2-3个),每个客户端控制台输出"连接服务器成功,可发送消息(输入exit退出)"。
- 在客户端输入消息(如"Hello BIO"),服务器端会打印客户端IP和消息,同时客户端会收到服务器的回复。
- 客户端输入"exit",即可退出,服务器端会打印"客户端断开连接"。
核心阻塞点:serverSocket.accept()(等待连接)、br.readLine()(读取数据),这两个方法会让线程阻塞,直到有连接或数据到来。
2.1.3 为什么说是 bio 是阻塞 io 呢?
首先我们先将上诉 demo 的服务端的线程去掉,不建立新线程。启动服务端,里面会通过一个 while 去不断地循环监听客户端连接,accept()方法会阻塞,直到有客户端发起连接。

第二创建两个客户端实例,在发送服务器消息的时候打个断点

debug 模式启动客户端 1
客户端提示成功

输入数据后卡在 debug 中

服务端也有显示连接

然后在启动另一个客户端实例(不用 debug)

对比一下,客户端 2 显示连接成功,但是服务端却没有响应。
当客户端 2 发起消息


服务端依旧不知道
直到 debug 放开就能够看到消息,但是客户端 2 的消息已经无法接受了,也就是连接丢失了。

那么我们可以将服务端改成在接收到客户端连接之后创建一个线程,这样是不是就解决了这个丢失的问题?那么,这么做又会有新的问题出来,那就是,当客户端有很多个,那就会创建十分多的线程,那又是一场灾难,所以聪明的你又想到了加上线程池来实现。(具体代码可以看 gitee-Trial2-Netty/Trial2-Netty-Base 模块下的代码)
*2.2 NIO:非阻塞 IO(重要)
2.2.1 NIO核心原理
NIO(New IO/Non-Blocking IO)即同步非阻塞IO,是JDK1.4引入的IO模型,用于解决BIO的高并发缺陷。其核心特点是:一个线程可处理多个客户端连接,线程在执行IO操作时不会被阻塞,若当前无IO事件(无连接、无数据),线程可去处理其他任务,无需等待。
通俗来说,NIO就像"一个服务员对应多个顾客",服务员(线程)不需要全程等待每个顾客,而是每隔一段时间巡视(轮询),查看哪个顾客有需求(IO事件),再去处理该顾客的需求,效率大幅提升。
NIO的核心组件(必须掌握),三者协同工作实现非阻塞通信:
- Channel(通道):替代BIO中的Socket(本质是 Socket 的包装),是双向的(可读可写),可配置为非阻塞模式,核心实现类有ServerSocketChannel(服务器端,负责连接,【 关注OP_ACCEPT 】)、SocketChannel(客户端,实际读写【关注OP_READ/OP_WRITE 】)。
- Buffer(缓冲区):NIO中所有数据的读写都必须通过缓冲区,本质是一块内存区域,用于临时存储数据(避免频繁IO调用,提升效率),核心实现类有ByteBuffer(最常用)、CharBuffer等。
- Selector(选择器):NIO的"灵魂",用于监听多个Channel的IO事件(连接就绪、读就绪、写就绪),一个Selector可绑定多个Channel,线程通过Selector轮询所有Channel,仅处理有IO事件的Channel,实现"单线程多连接"。
SelectionKey事件类型(四种事件),如下:
OP_READ:读事件
OP_WRITE:写事件
OP_CONNECT:连接事件(客户端专用)
OP_ACCEPT:接收连接事件(服务端专用)
于 NIO 代码对应如下
java
isAcceptable() → 是否有新连接可接受(OP_ACCEPT)
isReadable() → 是否有数据可读(OP_READ)
isWritable() → 是否可写(OP_WRITE)
isConnectable() → 连接是否完成(OP_CONNECT)
cancel() → 取消注册,让 Selector 不再监听该 Channel
关键规则:
ServerSocketChannel:仅关注OP_ACCEPT
客户端SocketChannel:连接→读写(动态调整关注事件)
服务端SocketChannel:仅关注OP_READ/OP_WRITE
反应堆模式(Reactor 模式):
这是 NIO 底层真正的灵魂,简单的说,反应堆模式 = 一个服务员 + 一个菜单 + 一堆顾客。
- 服务员 = Selector(选择器)
- 菜单 = 监听的事件(连接、读、写)
- 顾客 = 多个客户端连接(SocketChannel)
*用一个线程,同时处理成千上万的连接。
NIO的适用场景:
连接数多、并发量高但每个连接的数据传输量小的场景(如聊天服务器、电商秒杀系统),主流框架(Netty、Mina)均基于NIO封装。

1、应用程序写入数据
应用程序层的业务代码,把要发送的**业务数据写入 Buffer 缓冲区**。( 遵循规则:数据先入 Buffer,不直接写 Channel。)
2、Channel.write () 写数据到通道
应用程序调用
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">SocketChannel.write(Buffer)</font>,将 Buffer 中的数据**写入通道**。(Channel 是双向传输通道,此时数据从应用层交给网络传输层.)3、触发写事件 / 通道就绪
数据写入 Channel 后,Channel 触发**OP_WRITE 写就绪事件**,并把事件通知给 Selector。(Selector 会感知到这个 Channel 有写事件就绪。)
4、Selector.select () 返回
Selector 的select()是阻塞轮询方法,当任意一个注册的 Channel 有就绪事件(读 / 写 / 连接)时,select()立即返回,不再阻塞。(这是多路复用的核心:一个 select () 监听所有 Channel,不用为每个连接开线程。)
5、Channel 发送数据到网络
SocketChannel 将 Buffer 中的数据发送给网络层的客户端,完成网络数据输出。
6、触发 OP_READ 读就绪事件
当客户端向服务端发送回传数据 ,网络数据到达服务端的 SocketChannel 时,通道触发OP_READ 读就绪事件,并通知 Selector。
7、Selector 通知应用程序 "可读"
Selector 检测到
OP_READ就绪后,唤醒线程,通知应用程序:当前通道有数据可以读取。8、Channel.read () 读取网络数据
应用程序调用
SocketChannel.read(Buffer),将 Channel 中的网络数据读取到 Buffer 缓冲区。9、应用程序处理数据
应用程序从 Buffer 中读取数据,执行业务逻辑处理(解析、计算、响应等)。
10、客户端发起新连接请求
网络层的新客户端,向服务端的监听端口发起 TCP 连接请求。
11、触发 OP_ACCEPT 连接就绪事件
服务端的ServerSocketChannel(监听端口)感知到新连接,触发OP_ACCEPT 连接就绪事件,通知 Selector。
12、Selector 通知应用程序 "有新连接"
Selector 检测到
OP_ACCEPT就绪后,通知应用程序:有新客户端请求连接,服务端可以接受这个连接。(服务端接受连接后,会生成新的 SocketChannel,将其非阻塞模式下注册到 Selector,并绑定 OP_READ/OP_WRITE 事件,加入监听列表,实现新连接的管理。)
2.2.2 NIO 案例代码
案例说明:实现与BIO案例相同的功能(客户端发消息、服务器端收消息并回复),采用NIO模型,服务器端用单线程+Selector处理多个客户端连接,体现非阻塞优势。(在看代码之前,要先理解 NIO 的逻辑,其次,代码繁多,可以看我的 gitee 仓库,这里不会放太多代码。)
① 服务端代码
java
// 1. 定义端口
int port = 8889;
Selector selector = null;
ServerSocketChannel serverSocketChannel = null;
// 2. 创建Selector(选择器)
selector = Selector.open();
// 3. 创建ServerSocketChannel,绑定端口
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
// 4. 配置为非阻塞模式(核心:NIO必须设置为非阻塞)
serverSocketChannel.configureBlocking(false);
// 5. 将ServerSocketChannel注册到Selector,监听"连接就绪"事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
这里主要的时候要创建选择器-Selector,并且给 ServerSocketChannel 绑定服务器的端口号,待会客户端通过这个来访问,并且注册到选择器中,监听连接就绪的事件。
java
// 6. 循环轮询Selector,处理IO事件(非阻塞)
while (true) {
// 轮询Selector,获取有IO事件的通道数量(阻塞1秒,无事件则继续循环,避免空轮询消耗CPU)
int selectCount = selector.select(1000);
if (selectCount == 0) {
continue; // 无IO事件,继续轮询
}
// 7. 获取所有有IO事件的SelectionKey(事件令牌)
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 8. 遍历处理每个IO事件
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 移除当前key,避免重复处理
// 为什么需要移除?
// Selector不会自动清空selectedKeys集合, 需要程序员显式移除已处理的key
iterator.remove();
try {
// 处理"连接就绪"事件(客户端发起连接)
if (key.isAcceptable()) {
handleAccept(key, selector);
}
// 处理"读就绪"事件(客户端发送消息)
if (key.isReadable()) {
handleRead(key);
}
} catch (IOException e) {
// 处理单个客户端异常,不影响服务器继续运行
System.err.println("处理客户端IO异常:" + e.getMessage());
// 取消key并关闭channel
key.cancel();
if (key.channel() != null) {
try {
key.channel().close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
这段代码是 NIO 事件循环核心部分,实现了非阻塞 IO 的多路复用。
处理连接事件
java
private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
// 获取ServerSocketChannel
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 接收客户端连接(非阻塞,因为ServerSocketChannel已设为非阻塞)
SocketChannel clientChannel = serverSocketChannel.accept();
if (clientChannel != null) {
System.out.println("客户端连接成功:" + clientChannel.getRemoteAddress());
// 配置客户端Channel为非阻塞模式
clientChannel.configureBlocking(false);
// 分配缓冲区(大小1024字节,可根据需求调整)
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将客户端Channel注册到Selector,监听"读就绪"事件,并绑定缓冲区(附加对象)
clientChannel.register(selector, SelectionKey.OP_READ, buffer);
}
}
处理读就绪事件
java
private static void handleRead(SelectionKey key) throws IOException {
// 获取客户端SocketChannel
SocketChannel clientChannel = (SocketChannel) key.channel();
// 获取绑定的缓冲区(附加对象)
ByteBuffer buffer = (ByteBuffer) key.attachment();
// 读取客户端数据(非阻塞,无数据时返回-1)
int readLen = clientChannel.read(buffer);
if (readLen > 0) {
// 切换缓冲区为"读模式"(flip():将position重置为0,limit设为当前position)
buffer.flip();
// 将缓冲区数据转为字符串(去除首尾空白字符,包括换行符)
String clientMsg = new String(buffer.array(), 0, buffer.limit()).trim();
System.out.println("收到客户端[" + clientChannel.getRemoteAddress() + "]消息:" + clientMsg);
if ("exit".equals(clientMsg)) {
System.out.println("客户端[" + clientChannel.getRemoteAddress() + "]主动断开连接");
// 取消注册,关闭Channel
key.cancel();
clientChannel.close();
return;
}
// 回复客户端消息
String replyMsg = "服务器已收到消息:" + clientMsg;
// 清空缓冲区,准备写入回复数据
buffer.clear();
// 将回复消息写入缓冲区
buffer.put(replyMsg.getBytes());
// 切换缓冲区为"写模式"
buffer.flip();
// 写入客户端Channel(发送回复)
clientChannel.write(buffer);
// 清空缓冲区,准备下次读取
buffer.clear();
} else if (readLen == -1) {
// 读取到-1,表示客户端断开连接
System.out.println("客户端[" + clientChannel.getRemoteAddress() + "]断开连接");
// 取消注册,关闭Channel
key.cancel();
clientChannel.close();
}
}
② 客户端代码
客户端代码比较简单,需要设置缓冲区,写入数据的时候,实际上是写到缓冲区里面,在写到客户端SocketChannel 中。
java
public class NioClient {
public static void main(String[] args) {
String serverIp = "127.0.0.1";
int serverPort = 8889;
SocketChannel clientChannel = null;
ByteBuffer buffer = ByteBuffer.allocate(1024); // 缓冲区
try {
// 1. 创建SocketChannel
clientChannel = SocketChannel.open();
// 2. 配置为非阻塞模式
clientChannel.configureBlocking(false);
// 3. 连接服务器(非阻塞,连接未建立时不会阻塞线程)
clientChannel.connect(new InetSocketAddress(serverIp, serverPort));
// 循环等待连接建立(非阻塞连接,需判断连接状态)
while (!clientChannel.finishConnect()) {
// 连接未建立时,可做其他任务(此处简化,仅打印提示)
System.out.println("正在连接服务器...");
Thread.sleep(500); // 模拟其他任务
}
System.out.println("连接服务器成功,可发送消息(输入exit退出):");
// 4. 读取用户输入,发送消息
try (Scanner scanner = new Scanner(System.in)) {
String msg;
while (true) {
msg = scanner.nextLine();
if ("exit".equals(msg)) {
// 发送退出消息,关闭客户端
buffer.put(msg.getBytes());
buffer.flip();
clientChannel.write(buffer);
buffer.clear();
break;
}
// 发送消息到服务器
buffer.put(msg.getBytes());
buffer.flip(); // 切换为写模式
clientChannel.write(buffer);
buffer.clear(); // 清空缓冲区
// 接收服务器回复(非阻塞模式,需要等待数据到达)
// 等待服务器回复(最多等待3秒)
int retryCount = 0;
int maxRetry = 30; // 最多重试30次,每次100ms,共3秒
boolean receivedReply = false;
while (retryCount < maxRetry && !receivedReply) {
int readLen = clientChannel.read(buffer);
if (readLen > 0) {
buffer.flip();
String serverReply = new String(buffer.array(), 0, buffer.limit());
System.out.println("服务器回复:" + serverReply);
buffer.clear();
receivedReply = true;
} else if (readLen == 0) {
// 数据还未到达,等待一段时间后重试
Thread.sleep(100);
retryCount++;
} else {
// readLen == -1,服务器断开连接
System.out.println("服务器断开连接");
break;
}
}
if (!receivedReply && retryCount >= maxRetry) {
System.out.println("等待服务器回复超时");
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (clientChannel != null) {
try {
clientChannel.close();
System.out.println("客户端已退出");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行说明
- 先运行NioServer,控制台输出"NIO服务器已启动,监听端口:8889,等待客户端连接..."。
- 运行多个NioClient(可同时运行5-10个),每个客户端会打印"正在连接服务器...",连接成功后提示输入消息。
- 客户端输入消息,服务器端会接收并回复,多个客户端可同时发送消息,服务器端单线程即可处理(无卡顿)。
- 核心亮点:服务器端仅用一个主线程,通过Selector轮询所有客户端连接,无需创建大量线程,解决了BIO的高并发缺陷;所有IO操作均为非阻塞,线程无需等待。
启动一个服务端,两个客户端

此时会打印客户端连接成功

当我们在客户端 2 输出数据,服务端将会得到响应,客户端也能够得到服务端的回复。


当客户端 1 输出 exit 退出的时候,服务端不会挂掉,这里是做了处理(详细见 git 提交)

demo 做了异常捕获,当客户端程序断开的时候,会将异常捕获。

2.3 AIO:异步非阻塞IO
2.3.1 AIO核心原理
AIO(Asynchronous IO)即异步非阻塞IO,是JDK1.7引入的IO模型(也称为NIO.2),是真正意义上的"异步IO"。其核心特点是:线程发起IO操作后,立即返回,无需轮询等待,IO操作的整个过程(等待数据、读取数据)由操作系统在后台完成,当IO操作完成后,操作系统会主动通知线程,线程再处理结果。
通俗来说,AIO就像"顾客(客户端)通过手机下单,商家(服务器端)收到订单后处理,处理完成后主动通知顾客",商家(线程)无需主动询问顾客,全程无需等待,效率最高。
AIO与NIO的核心区别:NIO是"同步非阻塞",线程需要主动轮询Selector查看IO事件;AIO是"异步非阻塞",线程无需轮询,由操作系统主动通知IO事件完成,编程模型更简洁(无需手动管理Selector和Buffer的状态)。
AIO的核心实现:Java提供了两种AIO编程方式(CompletionHandler回调方式、Future方式),其中回调方式更常用,本文案例采用回调方式实现。
AIO的适用场景:连接数多、并发量高且IO操作耗时较长的场景(如文件服务器、视频点播系统),但由于Java AIO的底层实现依赖操作系统(Windows下性能较好,Linux下性能一般),实际开发中不如NIO(搭配Netty)常用,但作为IO模型的重要组成部分,需了解其核心思想。
2.3.2 AIO 案例代码
案例说明:实现与前面一致的功能,采用AIO模型(CompletionHandler回调方式),服务器端异步接收连接、异步读取数据,客户端异步连接、异步发送数据,线程无需阻塞和轮询。
① 服务端代码
java
/**
* AIO服务器端:异步非阻塞,基于CompletionHandler回调处理IO事件
* @author: lyd
* @date: 2026/2/10 23:52
*/
public class AioServer {
public static void main(String[] args) {
int port = 8890;
try {
// 1. 创建异步服务器通道
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
// 2. 绑定端口
serverChannel.bind(new InetSocketAddress(port));
System.out.println("AIO服务器已启动,监听端口:" + port + ",等待客户端连接...");
// 3. 异步接收客户端连接(无阻塞,发起后立即返回,连接完成后回调handle方法)
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
/**
* 连接成功时,回调此方法
* @param clientChannel 客户端通道(连接成功后生成)
* @param attachment 附加对象(此处为null)
*/
@Override
public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
// 继续接收下一个客户端连接(否则只能接收一个客户端)
serverChannel.accept(null, this);
try {
System.out.println("客户端连接成功:" + clientChannel.getRemoteAddress());
// 分配缓冲区,异步读取客户端消息(读取完成后回调ReadHandler)
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer, buffer, new ReadHandler(clientChannel));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 连接失败时,回调此方法
* @param throwable 异常信息
* @param attachment 附加对象
*/
@Override
public void failed(Throwable throwable, Object attachment) {
System.out.println("客户端连接失败:" + throwable.getMessage());
}
});
// 主线程不能退出(AIO的IO操作由操作系统后台处理,主线程退出则程序终止)
Thread.sleep(Integer.MAX_VALUE);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 自定义回调处理器:处理"读取客户端消息"的异步事件
*/
static class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel clientChannel;
// 构造方法,传入客户端通道
public ReadHandler(AsynchronousSocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
/**
* 读取数据成功时,回调此方法
* @param readLen 读取到的字节数(-1表示客户端断开连接)
* @param buffer 缓冲区(附加对象,存储读取到的数据)
*/
@Override
public void completed(Integer readLen, ByteBuffer buffer) {
if (readLen > 0) {
// 切换缓冲区为读模式,读取客户端消息
buffer.flip();
String clientMsg = new String(buffer.array(), 0, readLen);
try {
System.out.println("收到客户端[" + clientChannel.getRemoteAddress() + "]消息:" + clientMsg);
} catch (IOException e) {
e.printStackTrace();
}
// 回复客户端消息(异步写入,写入完成后回调WriteHandler)
String replyMsg = "服务器已收到消息:" + clientMsg;
buffer.clear();
buffer.put(replyMsg.getBytes());
buffer.flip();
clientChannel.write(buffer, buffer, new WriteHandler(clientChannel));
} else if (readLen == -1) {
// 客户端断开连接
try {
System.out.println("客户端[" + clientChannel.getRemoteAddress() + "]断开连接");
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取数据失败时,回调此方法
*/
@Override
public void failed(Throwable throwable, ByteBuffer buffer) {
System.out.println("读取客户端消息失败:" + throwable.getMessage());
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 自定义回调处理器:处理"回复客户端消息"的异步事件
*/
static class WriteHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel clientChannel;
public WriteHandler(AsynchronousSocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
/**
* 写入数据成功时,回调此方法
*/
@Override
public void completed(Integer writeLen, ByteBuffer buffer) {
// 写入成功后,继续异步读取客户端下一条消息
buffer.clear();
clientChannel.read(buffer, buffer, new ReadHandler(clientChannel));
}
/**
* 写入数据失败时,回调此方法
*/
@Override
public void failed(Throwable throwable, ByteBuffer buffer) {
System.out.println("回复客户端消息失败:" + throwable.getMessage());
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
② 客户端代码
java
/**
* AIO客户端:异步非阻塞,基于CompletionHandler回调处理IO事件
* @author: lyd
* @date: 2026/2/10 23:53
*/
public class AioClient {
public static void main(String[] args) {
String serverIp = "127.0.0.1";
int serverPort = 8890;
try {
// 1. 创建异步客户端通道
AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();
// 2. 异步连接服务器(连接完成后回调ConnectHandler)
clientChannel.connect(new InetSocketAddress(serverIp, serverPort), null, new CompletionHandler<Void, Object>() {
/**
* 连接成功回调
*/
@Override
public void completed(Void result, Object attachment) {
System.out.println("连接服务器成功,可发送消息(输入exit退出):");
// 读取用户输入,发送消息
try (Scanner scanner = new Scanner(System.in)) {
String msg;
while (true) {
msg = scanner.nextLine();
if ("exit".equals(msg)) {
// 发送退出消息,关闭客户端
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(msg.getBytes());
buffer.flip();
clientChannel.write(buffer, buffer, new ClientWriteHandler(clientChannel));
break;
}
// 异步发送消息到服务器(写入完成后回调ClientWriteHandler)
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(msg.getBytes());
buffer.flip();
clientChannel.write(buffer, buffer, new ClientWriteHandler(clientChannel));
}
}
}
/**
* 连接失败回调
*/
@Override
public void failed(Throwable throwable, Object attachment) {
System.out.println("连接服务器失败:" + throwable.getMessage());
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
// 主线程不能退出
Thread.sleep(Integer.MAX_VALUE);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 客户端写入消息回调处理器
*/
static class ClientWriteHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel clientChannel;
public ClientWriteHandler(AsynchronousSocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
/**
* 写入成功回调:写入成功后,异步读取服务器回复
*/
@Override
public void completed(Integer writeLen, ByteBuffer buffer) {
buffer.clear();
// 异步读取服务器回复(读取完成后回调ClientReadHandler)
clientChannel.read(buffer, buffer, new ClientReadHandler(clientChannel));
}
/**
* 写入失败回调
*/
@Override
public void failed(Throwable throwable, ByteBuffer buffer) {
System.out.println("发送消息失败:" + throwable.getMessage());
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 客户端读取服务器回复回调处理器
*/
static class ClientReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel clientChannel;
public ClientReadHandler(AsynchronousSocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
/**
* 读取成功回调:打印服务器回复
*/
@Override
public void completed(Integer readLen, ByteBuffer buffer) {
if (readLen > 0) {
buffer.flip();
String serverReply = new String(buffer.array(), 0, readLen);
System.out.println("服务器回复:" + serverReply);
} else if (readLen == -1) {
// 服务器断开连接
System.out.println("服务器已断开连接");
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取失败回调
*/
@Override
public void failed(Throwable throwable, ByteBuffer buffer) {
System.out.println("读取服务器回复失败:" + throwable.getMessage());
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行说明
- 先运行AioServer,控制台输出"AIO服务器已启动,监听端口:8890,等待客户端连接...",主线程进入休眠(不占用CPU)。
- 运行多个AioClient,每个客户端连接成功后提示输入消息,输入消息后,服务器端会异步接收并回复,客户端异步接收回复。
- 核心亮点:所有IO操作(连接、读、写)均为异步,服务器端和客户端线程无需阻塞、无需轮询,IO操作由操作系统后台完成,完成后通过回调函数通知线程处理结果,编程模型更简洁,资源消耗更低。
3 BIO、NIO、AIO核心对比
| 对比维度 | BIO(同步阻塞) | NIO(同步非阻塞) | AIO(异步非阻塞) |
|---|---|---|---|
| 核心模型 | 一个连接一个线程 | 单线程处理多个连接(Selector轮询) | 异步回调,操作系统通知IO完成 |
| 线程状态 | IO操作时线程阻塞 | 线程非阻塞,需轮询Selector | 线程完全不阻塞,无需轮询 |
| 核心组件 | Socket、ServerSocket、流 | Channel、Buffer、Selector | AsynchronousChannel、CompletionHandler |
| 编程难度 | 简单(入门首选) | 中等(需掌握三大组件) | 中等(回调模型,逻辑清晰) |
| 适用场景 | 连接数少、并发低(如本地测试) | 连接数多、并发高(如聊天、秒杀) | 连接数多、IO耗时久(如文件、视频) |
| 实际应用 | 简单工具、测试程序 | Netty、Mina框架(主流) | 少数高性能场景(Windows环境更优) |
总结
Socket是Java网络通信的基础,核心是"通信端点",分为客户端和服务器端,基于TCP/IP协议实现数据传输,掌握"连接→传输→关闭"的基本流程即可入门。BIO、NIO、AIO是Java网络编程的三种IO模型,演进方向是"从阻塞到非阻塞、从同步到异步",核心目标是提升并发处理能力、降低资源消耗。入门建议:先掌握BIO(理解Socket通信流程),再学习NIO(掌握三大组件,重点是Selector的使用),最后了解AIO(理解异步回调思想),无需急于掌握高级特性,先跑通案例,再逐步深入。实际开发中,NIO是主流(搭配Netty框架,封装了复杂的NIO细节),AIO由于操作系统兼容性问题,使用较少,但了解其异步思想,对后续学习高性能编程有很大帮助。
转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。转载请携带链接,转载到微信公众号请勿选择原创,谢谢!
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!