背景
本文是《Java 后端从小白到大神》修仙系列第十八篇,正式进入Java后端世界,本篇文章主要聊Java基础中的网络编程。若想详细学习请点击 首篇博文,我们开始吧。
文章概览
- 网络编程基础
- HTTP 协议详解
- WebSocket 协议
- Socket 编程(TCP/UDP)
- NIO 编程
- 网络编程最佳实践
一、网络编程基础
1.1 网络分层模型
网络通信采用分层架构,顺序由高到低,每一层都有明确的职责:
graph LR
classDef net fill:#ffd700,stroke:#333,color:black
classDef life fill:#ffd700,stroke:#333,color:black
subgraph 网络五层模型
direction TB
A1[应用层<br/>HTTP、DNS]
B1[传输层<br/>TCP、UDP]
C1[网际层<br/>IP、路由]
D1[网络接口层<br/>MAC、链路]
E1[物理层<br/>比特流、硬件]
end
subgraph 生活快递场景
direction TB
A2[快递包裹<br/>要寄的东西]
B2[快递员<br/>端到端送达]
C2[地址+导航<br/>寻址找路]
D2[公路/中转站<br/>分段运输]
E2[路面/光纤<br/>物理载体]
end
A1 <-->|数据内容| A2
B1 <-->|传输保障| B2
C1 <-->|寻址路由| C2
D1 <-->|分段接力| D2
E1 <-->|物理介质| E2
class A1,B1,C1,D1,E1 net
class A2,B2,C2,D2,E2 life
1.2 网络协议基础
| 协议 |
层次 |
作用 |
特点 |
| HTTP |
应用层 |
超文本传输 |
无状态、基于TCP |
| HTTPS |
应用层 |
安全HTTP |
加密传输 |
| WebSocket |
应用层 |
全双工通信 |
持久连接 |
| TCP |
传输层 |
可靠传输 |
面向连接、可靠 |
| UDP |
传输层 |
不可靠传输 |
无连接、快速 |
| IP |
网际层 |
寻址和路由 |
无连接 |
| ICMP |
网际层 |
网络控制 |
错误报告、网络探测 |
1.3 网络通信流程
一次客户端 → 服务器的请求,本质就是数据从上到下发出去 → 物理传输 → 从下到上收上来。
flowchart LR
%% 发送方:从上到下封装
subgraph 客户端
direction TB
A1[应用层]
B1[传输层<br>分片]
C1[网际层<br>加IP]
D1[网络接口层<br>成帧]
A1-->B1-->C1-->D1
end
%% 传输
D1 -- 比特流传输 --> D2
%% 接收方:从下到上解包
subgraph 服务器
direction BT
A2[应用层<br>处理]
B2[传输层<br>重组]
C2[网际层<br>拆IP]
D2[网络接口层<br>拆帧]
D2-->C2-->B2-->A2
end
classDef box fill:#f8f8f8,stroke:#666
class A1,B1,C1,D1,A2,B2,C2,D2 box
1.4 IP 地址与端口
- IP地址:标识网络中的设备(IPv4: 192.168.1.1, IPv6: 2001:db8::1)
- 端口:标识设备上的应用程序(0-65535,其中0-1023为系统保留)
- Socket地址:IP地址 + 端口号,唯一标识网络中的一个应用
二、HTTP 协议详解
2.1 HTTP 协议基础
HTTP(HyperText Transfer Protocol) 是用于客户端和服务器之间传输超文本的应用层协议。
核心特点:
- 无状态:默认不保留会话信息
- 基于TCP:使用TCP作为传输层协议
- 请求-响应模型:客户端发送请求,服务器返回响应
- 灵活可扩展:支持自定义头部和方法
2.2 HTTP 请求格式
HTTP 请求由 4 部分固定组成:
请求行 + 请求头 + 空行 + 请求体(可选)
请求行
1
|
GET /index.html HTTP/1.1
|
- 请求方法:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS
- 请求 URI:资源路径
- HTTP 版本:HTTP/1.0 / 1.1 / 2 / 3
请求头
1
2
3
4
5
6
|
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Cookie: sessionid=abc123
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
|
以键值对形式描述请求附加信息。
请求体
1
|
username=admin&password=123
|
GET 一般无请求体,POST/PUT 常用。
完整请求示例
1
2
3
4
5
6
|
POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
username=admin&password=123
|
2.3 HTTP 响应格式
HTTP 响应固定由 4 部分组成:
状态行 + 响应头 + 空行 + 响应体
状态行
- HTTP 版本:协议版本
- 状态码:200 成功、404 未找到、500 服务器错误等
- 状态描述:对状态码的文字说明
响应头
1
2
3
|
Content-Type: text/html
Content-Length: 20
Set-Cookie: sessionid=abc123
|
键值对结构,描述响应内容、长度、Cookie 等信息。
响应体
真正返回给客户端的数据(HTML、JSON、图片等)。
完整响应示例
1
2
3
4
5
6
|
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Set-Cookie: sessionid=abc123
<html>Welcome</html>
|
2.4 HTTP 状态码
| 类别 |
范围 |
含义 |
示例 |
| 信息性 |
100-199 |
临时响应 |
100 Continue |
| 成功 |
200-299 |
请求成功 |
200 OK, 201 Created |
| 重定向 |
300-399 |
需要进一步操作 |
301 Moved Permanently, 302 Found |
| 客户端错误 |
400-499 |
客户端请求错误 |
400 Bad Request, 404 Not Found |
| 服务器错误 |
500-599 |
服务器处理错误 |
500 Internal Server Error, 503 Service Unavailable |
2.5 HTTP 常用方法
| 方法 |
作用 |
幂等性 |
安全性 |
| GET |
获取资源 |
是 |
是 |
| POST |
提交数据 |
否 |
否 |
| PUT |
替换资源 |
是 |
否 |
| DELETE |
删除资源 |
是 |
否 |
| PATCH |
部分更新 |
否 |
否 |
| HEAD |
获取响应头 |
是 |
是 |
| OPTIONS |
查询支持的方法 |
是 |
是 |
2.6 Cookie 与 Session
Cookie
- 存储在客户端的小型文本数据
- 用于会话管理、个性化设置、用户行为跟踪
- 服务器通过
Set-Cookie 响应头设置
- 客户端下次请求通过
Cookie 请求头自动携带
Session
- 存储在服务器端的会话数据
- 每一个会话对应唯一的 Session ID
- Session ID 通常由 Cookie 携带,实现前后关联
示例
1
2
3
4
5
|
// 服务器设置 Cookie(携带 Session ID)
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure
// 客户端请求时带上 Cookie
Cookie: sessionid=abc123
|
核心区别
- Cookie:存在浏览器,小、不安全、可持久
- Session:存在服务器,大、更安全、依赖 Cookie
2.7 HTTP 客户端工具
按使用场景分为三类:
命令行工具 → Java 原生 API → 第三方开源库
2.7.1 命令行工具
适合快速测试、调试接口。
curl
命令格式
1
2
3
4
5
6
7
8
|
# 1. GET 请求(最简单,直接访问地址)
curl http://example.com
# 2. POST 请求(-X 指定方法,-d 携带参数)
curl -X POST -d "name=value" http://example.com
# 3. POST JSON 请求(-H 指定请求头,-d 传JSON数据)
curl -H "Content-Type: application/json" -d '{"name":"Alice"}' http://example.com
|
httpie(语法更友好)
1
2
3
4
5
|
# GET
http GET http://example.com
# POST JSON
http POST http://example.com name=Alice age=30
|
2.7.2 Java 原生工具
JDK 内置,无需额外依赖。
HttpURLConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class HttpURLConnectionExample {
public static void main(String[] args) {
try {
// 1. 创建 URL 对象(封装要访问的地址)
URL url = new URL("http://example.com");
// 2. 打开连接,强转为 HTTP 连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 3. 设置请求方法 GET/POST/PUT/DELETE
conn.setRequestMethod("GET");
// 4. 设置超时时间(单位:毫秒)
conn.setConnectTimeout(5000); // 连接超时
conn.setReadTimeout(5000); // 读取超时
// 5. 获取响应状态码 200=成功 404=找不到 500=服务器错误
int responseCode = conn.getResponseCode();
System.out.println("响应码: " + responseCode);
// 6. 读取服务器返回的数据(输入流)
try (BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
// 循环读取每一行数据
while ((line = in.readLine()) != null) {
response.append(line);
}
System.out.println("响应内容: " + response);
}
// 7. 关闭连接
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
2.7.3 第三方开源库
项目开发主流选择,功能更强、使用更简洁。
Apache HttpClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class ApacheHttpClientExample {
public static void main(String[] args) {
// 1. 创建 HttpClient 客户端(自动管理连接)
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 2. 创建 GET 请求
HttpGet request = new HttpGet("http://example.com");
// 3. 执行请求,获取响应对象
try (CloseableHttpResponse response = httpClient.execute(request)) {
// 4. 获取状态码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("响应码: " + statusCode);
// 5. 获取响应体内容(服务器返回的数据)
String responseBody = EntityUtils.toString(response.getEntity());
System.out.println("响应内容: " + responseBody);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
OkHttp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class OkHttpExample {
public static void main(String[] args) {
// 1. 创建 OkHttp 客户端
OkHttpClient client = new OkHttpClient();
// 2. 构建请求(指定 URL)
Request request = new Request.Builder()
.url("http://example.com")
.build();
// 3. 发送请求并获取响应
try (Response response = client.newCall(request).execute()) {
// 4. 判断是否成功
if (response.isSuccessful()) {
System.out.println("响应码: " + response.code());
System.out.println("响应内容: " + response.body().string());
} else {
System.out.println("请求失败: " + response.code());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
2.8 HTTP 与 HTTPS
| 对比项 |
HTTP |
HTTPS |
| 安全性 |
明文传输 |
加密传输 |
| 默认端口 |
80 |
443 |
| 证书 |
不需要 |
需要 CA 签发的 SSL/TLS 证书 |
| 性能 |
更快 |
略慢(握手和加密开销) |
| SEO |
低 |
高 |
| 适用场景 |
非敏感信息 |
登录、支付、API 等 |
三、WebSocket 协议
3.1 WebSocket 基础
WebSocket 是一种基于 TCP 的应用层协议,能够在客户端与服务器之间建立持久、全双工的通信连接。
核心特点
- 持久连接:一次握手成功后,连接长期保持,无需反复建立
- 全双工通信:客户端和服务器可以同时、双向发送数据
- 服务端主动推送:服务器可随时向客户端推送消息,不用客户端轮询
- 低开销、低延迟:数据帧头部很小(最小仅 2 字节)
- 兼容 HTTP:通过 HTTP 协议完成握手,默认使用 80/443 端口,易穿透防火墙
3.2 WebSocket 握手过程
WebSocket 连接建立的本质是:HTTP 协议升级。
- 客户端发送升级请求
1
2
3
4
5
6
|
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket # 声明要升级为 WebSocket
Connection: Upgrade # 标识连接需要升级
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13 # 协议版本
|
- 服务器响应协议切换
1
2
3
4
|
HTTP/1.1 101 Switching Protocols # 101 表示协议切换成功
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
- 握手完成
响应返回后,HTTP 协议升级为 WebSocket 协议,后续开始全双工数据传输。
3.3 WebSocket 数据帧格式
WebSocket 采用二进制帧传输数据,结构紧凑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
关键字段说明
- FIN:标识当前帧是否为消息的最后一帧
- Opcode:操作类型
0x1:文本数据
0x2:二进制数据
0x8:关闭连接
- Mask:是否启用掩码(客户端发送必须掩码,服务端不用)
- Payload Length:载荷数据长度
- Masking Key:掩码密钥(Mask=1 时存在)
- Payload Data:实际传输的业务数据
3.4 WebSocket 代码示例
使用 Java-WebSocket 开源库实现服务端与客户端。
3.4.1 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
/**
* WebSocket 服务端示例
* 监听端口,等待客户端连接,并收发消息
*/
public class WebSocketServerExample {
public static void main(String[] args) {
// 监听端口
int port = 8080;
// 创建 WebSocket 服务端
WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) {
// 客户端建立连接时触发
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
System.out.println("新客户端连接:" + conn.getRemoteSocketAddress());
// 向客户端发送欢迎消息
conn.send("欢迎连接 WebSocket 服务器!");
}
// 收到客户端消息时触发
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println("收到客户端消息:" + message);
// 回复消息
conn.send("服务器已收到:" + message);
}
// 连接关闭时触发
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("客户端断开连接,原因:" + reason);
}
// 发生异常时触发
@Override
public void onError(WebSocket conn, Exception ex) {
ex.printStackTrace();
}
};
// 启动服务
server.start();
System.out.println("WebSocket 服务已启动,监听端口:" + port);
}
}
|
3.4.2 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
/**
* WebSocket 客户端示例
* 连接服务端,发送消息并接收推送
*/
public class WebSocketClientExample {
public static void main(String[] args) throws Exception {
// 服务端地址(ws 是 WebSocket 协议)
URI serverUri = new URI("ws://localhost:8080");
WebSocketClient client = new WebSocketClient(serverUri) {
// 连接服务端成功时触发
@Override
public void onOpen(ServerHandshake handshakedata) {
System.out.println("连接服务端成功!");
send("客户端已上线");
}
// 收到服务端消息时触发
@Override
public void onMessage(String message) {
System.out.println("收到服务端消息:" + message);
}
// 连接关闭时触发
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("连接关闭:" + reason);
}
// 异常时触发
@Override
public void onError(Exception ex) {
ex.printStackTrace();
}
};
// 发起连接
client.connect();
// 等待连接建立
Thread.sleep(1000);
// 主动发送测试消息
client.send("Hello WebSocket");
// 保持程序运行
Thread.sleep(5000);
// 关闭连接
client.close();
}
}
|
3.5 WebSocket 典型应用场景
- 实时聊天:私聊、群聊、在线客服
- 游戏:实时状态同步、对战、弹幕
- 协同编辑:多人在线文档(如石墨、腾讯文档)
- 金融行情:股票、基金、币价实时推送
- 物联网监控:设备数据实时上报、远程控制
- 直播互动:弹幕、点赞、礼物实时展示
四、Socket 编程(TCP/UDP)
4.1 Socket 基础
Socket(套接字) 是网络通信的端点,是应用层与传输层之间的接口。
程序通过 Socket 实现跨主机、跨进程的数据通信。
网络编程核心类
Socket:客户端套接字(发起连接、收发数据)
ServerSocket:TCP 服务端套接字(监听端口、接受连接)
DatagramSocket:UDP 套接字(发送/接收报文)
DatagramPacket:UDP 数据包(数据载体)
InetAddress:IP 地址封装类
4.2 TCP 编程
TCP(传输控制协议):面向连接、可靠、字节流协议。
TCP 特点
- 面向连接:必须通过三次握手建立连接
- 可靠传输:保证数据不丢失、不重复、按顺序到达
- 流量控制 + 拥塞控制:保护网络与接收方
- 面向字节流:数据以流的形式传输
- 开销大、速度较慢
TCP 三次握手流程图
sequenceDiagram
participant C as 客户端
participant S as 服务器
Note over C,S: 第一次握手
C->>S: SYN, seq=x
Note right of C: 状态:SYN_SENT
Note over C,S: 第二次握手
S->>C: SYN+ACK, seq=y, ack=x+1
Note left of S: 状态:SYN_RCVD
Note over C,S: 第三次握手
C->>S: ACK, seq=x+1, ack=y+1
Note right of C: 状态:ESTABLISHED
Note left of S: 状态:ESTABLISHED
Note over C,S: 连接建立成功,开始传输数据
客户端发 SYN → 服务器回 SYN+ACK → 客户端回 ACK,连接建立。
关键参数说明
- SYN:同步标志位,请求建立连接
- ACK:确认标志位,确认收到
- seq:序列号(Sequence Number)
- ack:确认号(Acknowledgment Number)
TCP 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
/**
* TCP 服务端
* 功能:启动端口 -> 监听客户端连接 -> 多线程处理消息
*/
public class TCPServer {
public static void main(String[] args) {
int port = 12345;
// 1. 创建 ServerSocket,绑定端口,开始监听
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("TCP 服务器已启动,监听端口:" + port);
// 2. 循环接收客户端连接(支持多客户端)
while (true) {
// 阻塞等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("新客户端接入:" + socket.getInetAddress());
// 3. 为每个客户端创建独立线程处理(避免阻塞)
new Thread(new ClientHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端处理器:负责与单个客户端通信
*/
static class ClientHandler implements Runnable {
private final Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 4. 获取输入流(读客户端消息)、输出流(写回复)
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
String message;
// 5. 循环读取客户端消息
while ((message = in.readLine()) != null) {
System.out.println("收到客户端消息:" + message);
// 6. 回写消息
out.println("服务器回复:" + message);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 7. 关闭连接
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
|
TCP 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* TCP 客户端
* 功能:连接服务器 -> 发送消息 -> 接收回复
* 流程:在控制台打字 → scanner.readLine() 拿到 → out.println() 发给服务器 → 服务器处理后回复 → in.readLine() 收到回复 → System.out.println() 打印给你看
*/
public class TCPClient {
public static void main(String[] args) {
String serverIP = "localhost";
int port = 12345;
// 1. 创建 Socket 并连接服务器
try (Socket socket = new Socket(serverIP, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader scanner = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("成功连接服务器!");
String input;
// 2. 从控制台读取输入并发送
while ((input = scanner.readLine()) != null) {
out.println(input);
System.out.println("服务器回复:" + in.readLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
4.3 UDP 编程
UDP(用户数据报协议):无连接、不可靠、数据报协议。
UDP 特点
- 无连接:不需要三次握手,直接发数据包
- 不可靠:不保证到达、不保证顺序
- 面向报文:以数据包为单位传输
- 开销小、速度极快
- 适合实时场景
UDP 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/**
* UDP 服务端
* 功能:接收客户端报文 -> 回复消息
*/
public class UDPServer {
public static void main(String[] args) {
int port = 12345;
// 1. 创建 UDP Socket,绑定端口
try (DatagramSocket socket = new DatagramSocket(port)) {
System.out.println("UDP 服务器已启动,监听端口:" + port);
byte[] buffer = new byte[1024];
// 2. 循环接收数据报
while (true) {
// 3. 创建空数据包,接收数据
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet); // 阻塞等待
// 4. 解析消息
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到客户端消息:" + msg);
// 5. 准备回复
String reply = "服务器回复:" + msg;
byte[] replyData = reply.getBytes();
// 6. 获取客户端地址与端口,发送回复
InetAddress clientAddr = packet.getAddress();
int clientPort = packet.getPort();
DatagramPacket replyPacket = new DatagramPacket(
replyData, replyData.length, clientAddr, clientPort
);
socket.send(replyPacket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
UDP 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/**
* UDP 客户端
* 功能:发送数据报 -> 接收回复
*/
public class UDPClient {
public static void main(String[] args) {
String serverIP = "localhost";
int port = 12345;
try (DatagramSocket socket = new DatagramSocket()) {
// 1. 准备要发送的数据
String msg = "Hello UDP Server!";
byte[] data = msg.getBytes();
InetAddress address = InetAddress.getByName(serverIP);
// 2. 封装数据报(必须指定:服务器IP + 端口)
DatagramPacket sendPacket = new DatagramPacket(data, data.length, address, port);
socket.send(sendPacket);
System.out.println("已发送消息:" + msg);
// 3. 接收服务器回复
byte[] replyBuf = new byte[1024];
DatagramPacket replyPacket = new DatagramPacket(replyBuf, replyBuf.length);
socket.receive(replyPacket);
// 4. 解析回复
String reply = new String(replyPacket.getData(), 0, replyPacket.getLength());
System.out.println("服务器回复:" + reply);
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
4.4 TCP vs UDP
| 对比项 |
TCP |
UDP |
| 连接 |
面向连接 |
无连接 |
| 可靠性 |
可靠(不丢、不乱序) |
不可靠 |
| 传输方式 |
字节流 |
数据报 |
| 速度 |
较慢 |
极快 |
| 开销 |
大 |
小 |
| 应用场景 |
HTTP、文件传输、邮件 |
直播、游戏、语音、视频 |
五、NIO 与 NIO.2(AIO)编程
5.1 NIO 核心概念
NIO(Non-blocking I/O,非阻塞 I/O):Java 1.4 引入的同步非阻塞 I/O 模型。
核心三大件
- Buffer(缓冲区):数据的容器,读写都通过 Buffer
- Channel(通道):数据的传输通道,支持非阻塞
- Selector(多路复用器):一个线程管理多个 Channel
其他关键类
- SelectionKey:Channel 与 Selector 的注册关系,标识关注的事件
OP_ACCEPT:接受连接
OP_CONNECT:连接完成
OP_READ:可读
OP_WRITE:可写
NIO 核心概念 · 生活版类比
NIO 整体是什么?
以前 BIO 是:一个快递员只守一个客户,客户不说话,快递员就干等着。
现在 NIO 是:一个快递员同时守一堆客户,谁有动静就去处理谁,不用死等。
三大件类比
① Buffer(缓冲区)= 快递包裹 / 快递袋
- 所有数据都装在里面
- 要么往里装东西(写)
- 要么往外拿东西(读)
一句话:Buffer 就是装数据的包裹。
② Channel(通道)= 客户家门口的快递箱 / 收发通道
- 真正负责收发数据
- 可以非阻塞:没人发快递,快递员不用死等
- 一个通道对应一个连接(一个客户)
一句话:Channel 就是收发数据的通道,像快递收发口。
③ Selector(多路复用器)= 一个超级快递员
- 一个人盯着一大堆快递箱(Channel)
- 谁箱子有动静(有包裹要收/发),他就去处理谁
- 不用每个箱子配一个人
一句话:Selector 就是一个人管一堆通道的总管。
SelectionKey & 四个事件(最难记,用场景秒懂)
④ SelectionKey = 快递员手里的小本子
上面记着:哪个客户、要干嘛、有没有动静。
四个事件对应:
-
OP_ACCEPT 有人新上门要寄快递(新客户端来连接)
-
OP_CONNECT 客户那边快递通道搭好了(连接成功)
-
OP_READ 快递箱里有包裹到了,要取件(有数据可读)
-
OP_WRITE 快递箱空了,可以往里塞包裹发走(可以写数据)
极简总结
- NIO:一个人盯一堆客户,不傻等
- Buffer:装数据的包裹
- Channel:收发数据的快递箱
- Selector:盯一堆箱子的快递员
- SelectionKey:快递员的任务小本本
- 四个事件:来新客、连好了、有件取、能发件
快递站生活场景
- Selector 快递员:蓝色
- Channel 快递箱:绿色
- Buffer 包裹:橙色
- SelectionKey & 事件判断:紫色
- 客户端/连接:青色
flowchart TD
%% 样式定义
classDef selector fill:#1e88e5,color:#fff,stroke:#0d47a1
classDef channel fill:#43a047,color:#fff,stroke:#2e7d32
classDef buffer fill:#fb8c00,color:#fff,stroke:#e65100
classDef key fill:#8e24aa,color:#fff,stroke:#6a1b9a
classDef client fill:#00acc1,color:#fff,stroke:#00838f
A[客户端想连服务器]:::client --> B[ServerSocketChannel 开门]:::channel
B --> C[Selector 快递员开始巡逻]:::selector
C --> D[轮询所有 Channel 快递箱]:::channel
D --> E{有无事件?}:::key
E -- 无 --> D
E -- 有 --> F[拿到 SelectionKey 小本本]:::key
F --> G{判断事件类型}:::key
G -- OP_ACCEPT 新客户 --> H[建立新 SocketChannel 快递箱]:::channel
H --> I[注册到 Selector 继续巡逻]:::selector
I --> D
G -- OP_CONNECT 连接成功 --> J[连接就绪]:::client
J --> D
G -- OP_READ 可读 --> K[从 Channel 读数据到 Buffer 包裹]:::buffer
K --> L[业务处理]
L --> D
G -- OP_WRITE 可写 --> M[把 Buffer 数据写入 Channel 发走]:::buffer
M --> D
5.2 NIO 工作原理
一个 Selector 线程,通过轮询监听多个 Channel 的就绪事件,实现单线程处理多连接。
核心流程
- 创建
Selector
- 创建
ServerSocketChannel,绑定端口,配置非阻塞
- 注册到
Selector,关注 OP_ACCEPT
- 轮询:
selector.select() 阻塞等待事件
- 遍历
selectedKeys,处理对应事件
NIO 服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
/**
* NIO TCP 服务器
* 功能:单线程处理多客户端连接
*/
public class NIOServer {
public static void main(String[] args) {
int port = 12345;
// 1. 创建 Selector(多路复用器)
try (Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
// 2. 绑定端口
serverChannel.bind(new InetSocketAddress(port));
// 3. 配置为非阻塞(必须)
serverChannel.configureBlocking(false);
// 4. 注册到 Selector,关注 ACCEPT 事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO 服务器已启动,监听端口:" + port);
// 5. 轮询事件
while (true) {
// 阻塞等待至少一个 Channel 就绪
int readyCount = selector.select();
if (readyCount == 0) continue;
// 6. 遍历就绪的 Key
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// 7. 根据事件类型处理
if (key.isAcceptable()) {
// 处理新连接
acceptConnection(serverChannel, selector);
} else if (key.isReadable()) {
// 处理读数据
readData(key);
}
// 8. 必须移除已处理的 Key!
it.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 接受新连接
*/
private static void acceptConnection(ServerSocketChannel serverChannel, Selector selector) throws IOException {
SocketChannel clientChannel = serverChannel.accept();
System.out.println("新客户端接入:" + clientChannel.getRemoteAddress());
// 配置为非阻塞
clientChannel.configureBlocking(false);
// 注册到 Selector,关注 READ 事件
clientChannel.register(selector, SelectionKey.OP_READ);
}
/**
* 读取客户端数据
*/
private static void readData(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 用来拼接所有数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 循环读,直到读不到数据
while (true) {
buffer.clear();
int n = clientChannel.read(buffer);
if (n == -1) {
// 客户端关闭
clientChannel.close();
System.out.println("客户端断开连接");
return;
}
if (n == 0) {
// 暂时没数据了,读完了
break;
}
// 切换读模式
buffer.flip();
// 把本次读到的字节写入拼接流
byte[] part = new byte[buffer.remaining()];
buffer.get(part);
baos.write(part);
}
// 最终拿到完整数据
byte[] fullData = baos.toByteArray();
String msg = new String(fullData);
System.out.println("收到完整消息:" + msg);
// 回复
String reply = "服务器回复:" + msg;
ByteBuffer replyBuf = ByteBuffer.wrap(reply.getBytes());
clientChannel.write(replyBuf);
}
}
|
NIO 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* NIO TCP 客户端
* 功能:异步连接服务器 -> 控制台循环输入发送消息 -> 接收服务器回复
* 已修复:支持任意长度数据读取(循环读取 + 拼接)
*/
public class NIOClient {
public static void main(String[] args) {
String serverHost = "localhost";
int serverPort = 12345;
// 控制台输入扫描器,读取用户输入
BufferedReader consoleScanner = new BufferedReader(new InputStreamReader(System.in));
try (
// 1. 创建多路复用器 Selector
Selector selector = Selector.open();
// 2. 创建客户端 Socket 通道
SocketChannel socketChannel = SocketChannel.open()
) {
// 3. 核心配置:设置为非阻塞模式(NIO必须)
socketChannel.configureBlocking(false);
// 4. 发起连接请求(非阻塞,立即返回)
socketChannel.connect(new InetSocketAddress(serverHost, serverPort));
// 5. 注册通道到Selector,关注【连接完成】事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
System.out.println("NIO客户端已启动,正在连接服务器...");
// 6. 轮询处理事件
while (true) {
// 阻塞等待就绪的通道,超时1秒,避免卡死
int readyChannelCount = selector.select(1000);
// 遍历所有就绪的事件Key
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 必须移除已处理的Key,否则会重复触发
keyIterator.remove();
// 跳过无效的Key
if (!key.isValid()) continue;
// 事件1:连接完成事件
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel();
// 完成连接(必须调用,确认TCP三次握手完成)
if (channel.finishConnect()) {
System.out.println("成功连接到服务器!请输入消息发送:");
// 连接成功后,切换关注【可读事件】,等待服务器回复
key.interestOps(SelectionKey.OP_READ);
}
}
// 事件2:通道可读事件(服务器有回复过来)
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 用来拼接完整数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while (true) {
buffer.clear();
int n = channel.read(buffer);
if (n == -1) {
System.out.println("服务器断开连接");
channel.close();
return;
}
if (n == 0) {
// 读完了,没有更多数据
break;
}
// 切换读模式
buffer.flip();
byte[] part = new byte[buffer.remaining()];
buffer.get(part);
baos.write(part); // 拼接
}
// 拿到完整数据
byte[] fullData = baos.toByteArray();
String serverReply = new String(fullData);
System.out.println("服务器完整回复:" + serverReply);
System.out.print("请输入消息发送:");
}
}
// 检查控制台是否有用户输入,非阻塞检查
if (consoleScanner.ready()) {
String userInput = consoleScanner.readLine();
// 输入exit退出客户端
if ("exit".equalsIgnoreCase(userInput)) {
socketChannel.close();
System.out.println("客户端已退出");
return;
}
// 发送用户输入的消息到服务器
if (socketChannel.isConnected()) {
ByteBuffer sendBuffer = ByteBuffer.wrap(userInput.getBytes());
socketChannel.write(sendBuffer);
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端异常:" + e.getMessage());
}
}
}
|
5.3 NIO.2(AIO,异步 I/O)
NIO.2(AIO,Asynchronous I/O):Java 7 引入的真正异步非阻塞 I/O 模型。
核心特点
- 真正异步:操作提交后立即返回,由操作系统内核完成后回调通知
- 不需要 Selector
- 性能更高:适合高并发、高吞吐场景
核心类
AsynchronousServerSocketChannel:异步服务端 Channel
AsynchronousSocketChannel:异步客户端 Channel
CompletionHandler:异步操作完成后的回调接口
NIO.2 核心概念 · 生活版类比,餐厅点餐模式
角色对应
- 应用程序 = 顾客
- 操作系统内核 = 服务员
- 实际IO = 厨房做菜
- 回调 = 菜好了喊你
流程(真正异步)
- 你去餐厅,坐下点菜
- 你不用等菜做好,直接玩手机去
- 厨房自己做菜,操作系统(服务员)全程盯着
- 菜做好了,服务员主动过来喊你
- 你再过来吃(回调处理)
AIO 核心逻辑一句话
发起读写 → 立刻回去干别的 → 系统做完主动回调你
不用 Selector,不用循环轮询。
flowchart TD
A[应用程序发起异步IO请求] --> B[提交给操作系统内核]
B --> C[应用程序立刻返回,干别的事]
D[内核底层完成实际IO读写] --> E[IO操作结束]
E --> F[触发 CompletionHandler 回调]
F --> G[执行 completed 成功处理]
F --> H[执行 failed 失败处理]
G --> I[业务逻辑处理数据]
AIO 服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package com.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* AIO (NIO.2) 异步 TCP 服务端
* 特点:真正异步 + 操作系统内核回调 + 不需要 Selector
* 调用 read /write/accept 的那一瞬间,触发操作系统处理异步IO
*/
public class AIOServer {
public static void main(String[] args) throws Exception {
int port = 12345;
// 1. 打开异步服务端通道
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
// 2. 绑定端口,开始监听客户端连接
serverChannel.bind(new InetSocketAddress(port));
System.out.println("AIO 服务器已启动,监听端口:" + port);
// 3. 异步等待客户端连接【调用后立即返回】
// 由操作系统监控连接,客户端接入后自动回调 AcceptHandler
serverChannel.accept(null, new AcceptHandler(serverChannel));
// 保持主线程存活,否则服务器直接退出
Thread.sleep(Long.MAX_VALUE);
}
}
/**
* 客户端连接回调处理器
* 操作系统完成客户端连接后,自动回调 completed
*/
class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Void> {
private final AsynchronousServerSocketChannel serverChannel;
public AcceptHandler(AsynchronousServerSocketChannel serverChannel) {
this.serverChannel = serverChannel;
}
@Override
public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
// 关键:必须再次调用 accept,才能继续接收下一个客户端
serverChannel.accept(null, this);
try {
System.out.println("新客户端接入:" + clientChannel.getRemoteAddress());
} catch (IOException e) {
e.printStackTrace();
}
// 每个客户端独立分配:缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 异步读取客户端数据【调用后立即返回】
// 操作系统读取数据完成后,自动回调 ReadHandler
clientChannel.read(buffer, buffer, new ReadHandler(clientChannel));
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("客户端连接失败");
exc.printStackTrace();
}
}
/**
* 客户端数据读取回调处理器
* 操作系统将数据读入缓冲区后,自动回调 completed
* 支持:大数据自动拼接(>1024字节也能完整接收)
*/
class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel clientChannel;
public ReadHandler(AsynchronousSocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
@Override
public void completed(Integer bytesRead, ByteBuffer buf) {
try {
// 客户端关闭连接,返回 -1
if (bytesRead == -1) {
System.out.println("客户端断开连接");
clientChannel.close();
return;
}
// 切换缓冲区为读模式
buf.flip();
// 获取缓冲区中的数据
byte[] segment = new byte[buf.remaining()];
buf.get(segment);
// 打印消息
String msg = new String(segment).trim();
System.out.println("收到客户端消息:" + msg);
// 回复客户端
String reply = "服务器已收到:" + msg;
ByteBuffer writeBuf = ByteBuffer.wrap(reply.getBytes());
clientChannel.write(writeBuf);
// 清空缓冲区,继续异步读取下一段数据
buf.clear();
clientChannel.read(buf, buf, this);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("数据读取异常");
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
AIO 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
package com.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* AIO 异步 TCP 客户端
* 功能:异步连接服务器 -> 控制台循环发送消息 -> 异步接收服务器回复
*/
public class AIOClient {
// 客户端通道,全局持有方便发送消息
private static AsynchronousSocketChannel clientChannel;
public static void main(String[] args) throws Exception {
String serverHost = "localhost";
int serverPort = 12345;
// 控制台输入扫描器
BufferedReader consoleScanner = new BufferedReader(new InputStreamReader(System.in));
// 1. 创建异步客户端通道
clientChannel = AsynchronousSocketChannel.open();
System.out.println("AIO客户端已启动,正在连接服务器...");
// 2. 异步发起连接,通过CompletionHandler回调处理结果
clientChannel.connect(new InetSocketAddress(serverHost, serverPort), null, new CompletionHandler<Void, Void>() {
// 连接成功回调
@Override
public void completed(Void result, Void attachment) {
System.out.println("成功连接到服务器!请输入消息发送:");
// 连接成功后,立刻启动异步读操作,监听服务器回复
startRead();
}
// 连接失败回调
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("连接服务器失败:" + exc.getMessage());
exc.printStackTrace();
}
});
// 3. 主线程循环读取控制台输入,发送消息
while (true) {
String userInput = consoleScanner.readLine();
// 输入exit退出
if ("exit".equalsIgnoreCase(userInput)) {
if (clientChannel.isOpen()) {
clientChannel.close();
}
System.out.println("客户端已退出");
System.exit(0);
}
// 通道已连接才发送消息
if (clientChannel != null && clientChannel.isOpen()) {
ByteBuffer sendBuffer = ByteBuffer.wrap(userInput.getBytes());
// 异步写消息,发送完成后回调
clientChannel.write(sendBuffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {
// 发送成功,无需额外处理,等待服务器回复即可
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("消息发送失败:" + exc.getMessage());
}
});
}
}
}
/**
* 异步读取服务器回复的核心方法
*/
private static void startRead() {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
// 异步读操作,收到数据后触发回调
clientChannel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
// 读取成功回调
@Override
public void completed(Integer readBytes, ByteBuffer buffer) {
// 读取到-1,代表服务器关闭连接
if (readBytes == -1) {
try {
clientChannel.close();
System.out.println("\n服务器已断开连接");
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
return;
}
// 缓冲区切换为读模式
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String serverReply = new String(data);
// 打印服务器回复
System.out.println("\n服务器回复:" + serverReply);
System.out.print("请输入消息发送:");
// 清空缓冲区,继续监听下一次服务器回复
buffer.clear();
clientChannel.read(buffer, buffer, this);
}
// 读取失败回调
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("读取消息失败:" + exc.getMessage());
exc.printStackTrace();
}
});
}
}
|
5.4 BIO vs NIO vs AIO 对比
所有 IO 模型通用核心
无论 BIO、NIO、AIO,数据传输的核心逻辑完全一致:
- 通道(Channel)是数据传输的唯一“道路”
- 服务端、客户端都有 Channel(NIO 和 AIO 完全一致,BIO 底层也有类似通道机制,只是未暴露);
- 通道是双向的,既能发送数据,也能接收数据。
- 读写数据的核心规则
- channel.write(…) → 往通道里写数据 = 发送数据(自己→对方);
- channel.read(…) → 从通道里读数据 = 接收数据(对方→自己);
- 无论 NIO、AIO,这个规则完全统一,没有任何区别。
NIO vs AIO 核心区别
NIO 和 AIO 最本质的区别,不是通道不一样,而是等待数据的方式不一样:
NIO(非阻塞同步):自己找数据
- 核心:用 Selector(多路复用器/快递员),主动轮询所有通道,询问“有没有数据”;
- 特点:不阻塞主线程,但需要自己持续检查,还是要“主动操心”;
- 类比:自己去快递站,挨个问快递员“我的快递到了吗”。
AIO(非阻塞异步):别人送数据通知你
- 核心:不用 Selector,调用 read/write/accept 后,直接交给操作系统,自己去干别的;
- 特点:完全不操心,操作系统完成 IO 操作后,主动回调通知你“数据好了”;
- 类比:快递员把快递放家门口,主动给你打电话“快递到了,来取”。
BIO 补充
BIO(阻塞同步):最原始的模型,一连接一线程,全程阻塞,直到 IO 完成才能干别的;
- 核心:没有多路复用,没有异步回调,简单但效率低;
- 和 NIO/AIO 对比:通道逻辑一致(write 发、read 收),但等待数据时会“卡死”,不适合高并发。
六、网络编程最佳实践
6.1 实用优化建议
- 缓冲区
合理设置 ByteBuffer 大小,避免频繁创建/销毁,减少 GC。
- 线程模型
不要在 IO 线程做耗时业务,用独立线程池处理逻辑。
- 连接管理
及时关闭无效连接,避免资源泄漏;长连接建议加心跳。
- 数据处理
TCP 是流式传输,大数据必须拼接,否则会丢包、截断。
6.2 编码与安全要点
- 所有网络输入必须校验,避免非法数据攻击。
- 敏感传输使用 TLS/SSL 加密。
- 统一异常处理,保证连接、缓冲区、通道正常关闭。
6.3 常用工具
- 抓包:Wireshark、tcpdump
- JVM 监控:VisualVM
- 压测:ab、JMeter
- 日志:Logback / Log4j
6.4 技术选型
- 连接少、简单场景 → BIO
- 高并发、长连接 → NIO
- 极致异步、重 IO → AIO
- 生产高性能 → 直接用 Netty(封装 NIO,不用重复造轮子)
七. 总结
- Channel 是数据传输通道,服务端、客户端都有。
- write = 发送数据,read = 接收数据。
- BIO / NIO / AIO 读写逻辑一致,区别只在于等待数据的方式:
- BIO:死等
- NIO:自己轮询等(Selector)
- AIO:交给操作系统,完成后回调通知
网络编程是 Java 后端开发的重要组成部分,掌握好网络编程技能对于构建高性能、可靠的后端系统至关重要。通过本文的学习,相信你已经对 Java 网络编程有了更深入的理解,可以在实际项目中灵活应用各种网络技术。