Java 网络编程学习笔记
一句话定位:Java 网络编程涵盖 Socket、BIO/NIO/AIO 模型、Reactor 模式与 HttpClient/Netty 高性能通信实战。
一、Socket 编程基础
1.1 什么是 Socket
Socket 是操作系统提供的进程间通信机制,是 TCP/IP 网络通信的端点抽象。Java 中通过 java.net 包提供 Socket 编程支持。
┌─────────────┐ TCP/IP 网络 ┌─────────────┐
│ Client │ ────────────────────────▶ │ Server │
│ Socket │ ◀──────────────────────── │ ServerSocket│
│ (IP:Port) │ 双向数据流 │ (IP:Port) │
└─────────────┘ └─────────────┘
1.2 ServerSocket 服务端示例
import java.io.*;
import java.net.*;
public class SimpleServer {
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("Server started on port 8080");
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress());
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
clientSocket.getOutputStream(), true);
String line;
while ((line = in.readLine()) != null) {
System.out.println("Received: " + line);
out.println("Echo: " + line);
}
}
}
}
1.3 Socket 客户端示例
import java.io.*;
import java.net.*;
public class SimpleClient {
public static void main(String[] args) throws IOException {
try (Socket socket = new Socket("localhost", 8080)) {
PrintWriter out = new PrintWriter(
socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
out.println("Hello, Server!");
String response = in.readLine();
System.out.println("Server response: " + response);
}
}
}
1.4 常用配置选项
| 方法 |
说明 |
默认值 |
setSoTimeout(int ms) |
读取超时时间 |
0 (永不超时) |
setTcpNoDelay(boolean on) |
禁用 Nagle 算法 |
false |
setReuseAddress(boolean on) |
地址复用 |
false |
setKeepAlive(boolean on) |
心跳保活 |
false |
setReceiveBufferSize(int size) |
接收缓冲区大小 |
取决于 OS |
setSendBufferSize(int size) |
发送缓冲区大小 |
取决于 OS |
setSoLinger(boolean on, int sec) |
关闭时等待发送完成 |
禁用 |
二、多线程服务器模型(BIO)
2.1 BIO 模型特点
BIO(Blocking I/O)是 Java 最传统的 I/O 模型:
- 每个连接独占一个线程
- 线程在 read/write 操作时阻塞
- 模型简单,编程直观
- 线程数 = 连接数,高并发下资源消耗大
2.2 线程池版多线程服务器
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class BioThreadPoolServer {
private static final int PORT = 8080;
private static final int THREAD_POOL_SIZE = 50;
public static void main(String[] args) throws IOException {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("BIO Server started on port " + PORT);
while (!Thread.currentThread().isInterrupted()) {
Socket clientSocket = serverSocket.accept();
System.out.println("New connection: " + clientSocket.getRemoteSocketAddress());
executor.submit(() -> handleClient(clientSocket));
}
}
}
private static void handleClient(Socket socket) {
try (BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
socket.getOutputStream(), true)) {
String line;
while ((line = in.readLine()) != null) {
if ("bye".equalsIgnoreCase(line)) break;
out.println("Echo: " + line);
}
} catch (IOException e) {
System.err.println("Client error: " + e.getMessage());
} finally {
try { socket.close(); } catch (IOException ignored) {}
}
}
}
2.3 BIO 模型局限性
线程数 vs 连接数
┌──────────────────────────────────────┐
│ 连接数 线程数 内存消耗 CPU 利用率 │
│ 100 100 ~100MB 低 │
│ 1000 1000 ~1GB 中 │
│ 10000 10000 ~10GB 高(切换) │
│ 100000 不可行 OOM 崩溃 │
└──────────────────────────────────────┘
每个线程默认占用 ~1MB 栈空间,10000 个连接就需要约 10GB 内存,这是 BIO 无法应对高并发的根本原因。
三、NIO 网络编程(Selector + Channel)
3.1 NIO 核心概念
Java NIO(Non-blocking I/O)在 Java 1.4 引入,位于 java.nio 包:
| 组件 |
说明 |
| Channel |
通道,双向数据传输,类似 Stream 但可非阻塞 |
| Buffer |
缓冲区,数据容器,通过 position/limit/capacity 管理 |
| Selector |
选择器,单线程监控多个 Channel 的 I/O 事件 |
3.2 Buffer 使用
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((byte) 1);
buffer.put("Hello".getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
3.3 NIO 服务器完整示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NioServer {
private static final int PORT = 8080;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(PORT));
System.out.println("NIO Server started on port " + PORT);
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
handleAccept(key, serverChannel, selector);
} else if (key.isReadable()) {
handleRead(key);
} else if (key.isWritable()) {
handleWrite(key);
}
iterator.remove();
}
}
}
private static void handleAccept(SelectionKey key,
ServerSocketChannel serverChannel, Selector selector) throws IOException {
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ,
ByteBuffer.allocate(BUFFER_SIZE));
System.out.println("Client connected: " + clientChannel.getRemoteAddress());
}
private static void handleRead(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
channel.close();
key.cancel();
return;
}
buffer.flip();
String message = new String(buffer.array(), 0, buffer.remaining());
System.out.println("Received: " + message);
buffer.compact();
key.interestOps(SelectionKey.OP_WRITE);
}
private static void handleWrite(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.flip();
channel.write(buffer);
key.interestOps(SelectionKey.OP_READ);
}
}
3.4 NIO 四种事件类型
| 事件常量 |
值 |
说明 |
OP_ACCEPT |
16 |
客户端连接请求(仅 ServerSocketChannel) |
OP_CONNECT |
8 |
连接建立完成(仅 SocketChannel) |
OP_READ |
1 |
通道可读 |
OP_WRITE |
4 |
通道可写 |
四、AIO 异步 I/O(NIO.2)
4.1 AIO 概述
AIO(Asynchronous I/O)在 Java 7 以 NIO.2 引入,位于 java.nio.channels 包:
- 真正的异步 I/O,调用后立即返回,完成后通过回调/ Future 通知
- 依赖操作系统支持(Linux 下依赖 libaio,Windows 下使用 IOCP)
- Linux 上 AIO 支持有限,实际生产中较少使用
4.2 AIO 服务器示例(Future 方式)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.concurrent.Future;
public class AioServer {
private static final int PORT = 8080;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) throws Exception {
AsynchronousChannelGroup group = AsynchronousChannelGroup
.withFixedThreadPool(10, Executors.defaultThreadFactory());
AsynchronousServerSocketChannel serverChannel =
AsynchronousServerSocketChannel.open(group)
.bind(new InetSocketAddress(PORT));
System.out.println("AIO Server started on port " + PORT);
while (true) {
Future<AsynchronousSocketChannel> future = serverChannel.accept();
AsynchronousSocketChannel clientChannel = future.get();
System.out.println("Client connected: " + clientChannel.getRemoteAddress());
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
Future<Integer> readFuture = clientChannel.read(buffer);
int bytesRead = readFuture.get();
buffer.flip();
String message = new String(buffer.array(), 0, bytesRead);
System.out.println("Received: " + message);
}
}
}
4.3 AIO 服务器示例(CompletionHandler 回调方式)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
public class AioCallbackServer {
private static final int PORT = 8080;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) throws IOException {
AsynchronousServerSocketChannel serverChannel =
AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(PORT));
System.out.println("AIO Callback Server started on port " + PORT);
acceptConnection(serverChannel);
Thread.currentThread().join();
}
private static void acceptConnection(AsynchronousServerSocketChannel server) {
server.accept(null, new CompletionHandler<
AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object att) {
acceptConnection(server);
handleClient(client);
}
@Override
public void failed(Throwable exc, Object att) {
System.err.println("Accept failed: " + exc.getMessage());
}
});
}
private static void handleClient(AsynchronousSocketChannel client) {
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
client.read(buffer, buffer, new CompletionHandler<
Integer, ByteBuffer>() {
@Override
public void completed(Integer bytesRead, ByteBuffer buf) {
if (bytesRead == -1) {
try { client.close(); } catch (IOException ignored) {}
return;
}
buf.flip();
String msg = new String(buf.array(), 0, bytesRead);
System.out.println("Received: " + msg);
}
@Override
public void failed(Throwable exc, ByteBuffer att) {
System.err.println("Read failed: " + exc.getMessage());
}
});
}
}
五、BIO / NIO / AIO 三种模型对比
5.1 同步与异步、阻塞与非阻塞
理解 I/O 模型的关键在于两个维度:
同步 vs 异步
┌────────────────────────────────────────────────────┐
│ 同步 (Synchronous) : 调用者主动参与 I/O 操作 │
│ - 阻塞式:调用线程挂起等待(BIO) │
│ - 非阻塞式:立即返回,轮询状态(NIO) │
│ │
│ 异步 (Asynchronous) : 操作系统完成 I/O 后通知调用者│
│ - 回调或 Future 获取结果(AIO) │
└────────────────────────────────────────────────────┘
生活比喻:烧开水
┌────────────────────────────────────────────────────┐
│ BIO : 盯着水壶,水开了才离开(阻塞等待) │
│ NIO : 每隔几秒去看一下水壶(非阻塞轮询) │
│ AIO : 水壶响了自动通知你(异步回调) │
└────────────────────────────────────────────────────┘
5.2 详细对比表
| 维度 |
BIO |
NIO |
AIO |
| 全称 |
Blocking I/O |
Non-blocking I/O |
Asynchronous I/O |
| 引入版本 |
JDK 1.0 |
JDK 1.4 |
JDK 7 |
| 包名 |
java.net |
java.nio |
java.nio.channels |
| 核心组件 |
Socket/ServerSocket |
Channel/Buffer/Selector |
AsynchronousChannel |
| 通信方式 |
流(Stream) |
缓冲区(Buffer) |
缓冲区(Buffer) |
| 阻塞方式 |
阻塞 |
非阻塞 |
异步 |
| 线程模型 |
一连接一线程 |
多路复用单/少线程 |
回调/未来 |
| 适用场景 |
连接数少且固定 |
高并发、短连接 |
高并发、长连接、文件 I/O |
| 编程难度 |
低 |
中 |
高 |
| Linux 支持 |
良好 |
epoll 完善 |
有限(依赖 libaio) |
| 代表框架 |
Tomcat (BIO模式) |
Netty, Tomcat (NIO) |
较少使用 |
| 吞吐量 |
低 |
高 |
高 |
| 延迟 |
低(无轮询开销) |
略高(多路复用) |
低(回调触发) |
5.3 选择建议
| 场景 |
推荐模型 |
理由 |
| 内部工具,< 100 并发 |
BIO |
简单可靠,够用 |
| Web 服务器,高并发 |
NIO |
生态成熟,性能优秀 |
| 网关/代理 |
NIO |
连接数巨大,需多路复用 |
| 文件异步读写 |
AIO |
OS 原生异步支持 |
| 企业级应用 |
NIO(通过 Netty) |
生态最完善 |
六、Reactor 模型
6.1 什么是 Reactor 模型
Reactor 是一种事件驱动的网络编程设计模式,核心思想是将 I/O 事件的分发与处理解耦。Doug Lea 在《Scalable IO in Java》中详细阐述了这一模型。
6.2 单线程 Reactor
┌─────────────────────────────────────────┐
│ Single Thread Reactor │
│ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Acceptor │───▶ │ Selector │ │
│ └──────────┘ │ (Event Loop)│ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ Handler │ │
│ │ (Read/Decode│ │
│ │ Compute/ │ │
│ │ Encode/Send)│ │
│ └──────────────┘ │
└─────────────────────────────────────────┘
特点:单线程完成 accept → read → decode → compute → encode → send
优点:简单,无线程切换开销
缺点:无法利用多核,Handler 阻塞影响整体
适用:单机少量连接,简单协议
6.3 多线程 Reactor
┌─────────────────────────────────────────────────┐
│ Multi-Thread Reactor │
│ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Acceptor │───▶ │ Selector │ │
│ └──────────┘ │ (Event Loop)│ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ Handler │ │
│ │ (Read/ │ │
│ │ Decode) │ │
│ └──────┬───────┘ │
│ │ dispatch │
│ ┌──────▼──────────────────┐ │
│ │ Worker Thread Pool │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ T1 │ │ T2 │ │ T3 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ └────┘ └────┘ └────┘ │ │
│ │ (Compute/Encode/Send) │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────────────┘
特点:Selector 线程负责 I/O 事件,业务处理交给线程池
优点:I/O 不阻塞业务计算,可充分利用多核
缺点:Acceptor 仍为单点
适用:大多数业务服务器
6.4 主从多线程 Reactor(Master-Slave)
┌───────────────────────────────────────────────────────────┐
│ Master-Slave Multi-Thread Reactor │
│ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ MainReactor │ │ SubReactors │ │
│ │ (Acceptor) │ │ ┌───────────────┐ │ │
│ │ │ │ │ SubReactor-1 │ │ │
│ │ ┌──────────┐ │ │ │ (Selector + │ │ │
│ │ │Selector │───┼───▶│ │ Handler) │ │ │
│ │ │(accept) │ │ │ └───────┬───────┘ │ │
│ │ └──────────┘ │ │ │ │ │
│ └─────────────────┘ │ ┌───────▼───────┐ │ │
│ │ │ SubReactor-2 │ │ │
│ 监听连接事件 │ │ (Selector + │ │ │
│ 分发给 SubReactor │ │ Handler) │ │ │
│ │ └───────┬───────┘ │ │
│ │ │ │ │
│ │ ┌───────▼───────┐ │ │
│ │ │ SubReactor-N │ │ │
│ │ │ (Selector + │ │ │
│ │ │ Handler) │ │ │
│ │ └───────┬───────┘ │ │
│ └──────────┬──────────┘ │
│ │ dispatch │
│ ┌──────────▼──────────────────┐ │
│ │ Worker Thread Pool │ │
│ │ (Business Logic Processing)│ │
│ └─────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
特点:MainReactor 只管 accept,SubReactor 负责 I/O 读写,
Worker 线程池处理业务逻辑
优点:accept 与 I/O 分离,连接处理能力线性扩展
缺点:架构复杂,调试困难
适用:高并发网关、IM 服务器、RPC 框架
6.5 Reactor 模型对比总结
| 维度 |
单线程 |
多线程 |
主从多线程 |
| Reactor 数量 |
1 |
1 |
1 + N |
| Accept 处理 |
同线程 |
同线程 |
独立 MainReactor |
| I/O 处理 |
同线程 |
同线程 |
多个 SubReactor |
| 业务处理 |
同线程 |
线程池 |
线程池 |
| 多核利用 |
否 |
部分 |
是 |
| 连接上限 |
低 |
中 |
高 |
| 复杂度 |
低 |
中 |
高 |
| 典型使用 |
简单工具 |
Tomcat NIO |
Netty 默认模式 |
七、Java 11+ HttpClient(java.net.http)
7.1 概述
Java 11 引入了全新的 java.net.http.HttpClient API,替代老旧的 HttpURLConnection:
- 支持 HTTP/1.1 和 HTTP/2
- 支持同步和异步请求
- 支持 WebSocket
- API 设计现代,链式调用
7.2 基本使用
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
public class HttpClientDemo {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(5))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}
7.3 异步请求
CompletableFuture<HttpResponse<String>> future = client.sendAsync(request,
HttpResponse.BodyHandlers.ofString());
future.thenAccept(response -> {
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}).exceptionally(e -> {
System.err.println("Request failed: " + e.getMessage());
return null;
});
7.4 POST 请求示例
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"name\":\"test\",\"age\":25}"))
.build();
HttpResponse<String> response = client.send(postRequest,
HttpResponse.BodyHandlers.ofString());
7.5 文件上传/下载
HttpRequest downloadReq = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/file.zip"))
.build();
HttpResponse<Path> downloadResp = client.send(downloadReq,
HttpResponse.BodyHandlers.ofFile(
Path.of("downloaded_file.zip")));
HttpRequest uploadReq = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/upload"))
.POST(HttpRequest.BodyPublishers.ofFile(
Path.of("upload_file.zip")))
.build();
7.6 常用配置选项
| 配置项 |
说明 |
示例 |
version() |
HTTP 协议版本 |
HTTP_1_1 / HTTP_2 |
connectTimeout() |
连接超时 |
Duration.ofSeconds(10) |
followRedirects() |
重定向策略 |
NORMAL / ALWAYS / NEVER |
proxy() |
代理设置 |
ProxySelector.of(...) |
executor() |
异步任务线程池 |
Executors.newFixedThreadPool(...) |
sslContext() |
SSL/TLS 配置 |
SSLContext.getDefault() |
八、Netty 简介
8.1 为什么需要 Netty
虽然 Java NIO 提供了多路复用能力,但原生 NIO 编程存在诸多痛点:
| 痛点 |
说明 |
| API 复杂 |
Selector/Channel/Buffer 组合使用繁琐 |
| Bug 多 |
JDK NIO 的 epoll bug(空轮询导致 CPU 100%) |
| 拆包/粘包 |
TCP 流式传输需自行处理消息边界 |
| 编解码 |
需要手动实现协议的 encode/decode |
| 断线重连 |
需要自行实现连接管理 |
| 性能调优 |
缓冲区管理、内存池等需深入理解 |
| SSL/TLS |
集成复杂 |
8.2 Netty 核心概念
Netty 架构分层
┌────────────────────────────────────────────┐
│ Application Layer │
│ (Handler / Business Logic) │
├────────────────────────────────────────────┤
│ Pipeline / ChannelHandler │
│ (Inbound: 解码、业务处理) │
│ (Outbound: 编码、发送) │
├────────────────────────────────────────────┤
│ EventLoopGroup │
│ (BossGroup: accept 连接) │
│ (WorkerGroup: 读写 I/O) │
├────────────────────────────────────────────┤
│ Channel │
│ (NioSocketChannel / NioServerSocketChannel) │
├────────────────────────────────────────────┤
│ Transport │
│ (NIO / OIO / Local / Embedded) │
└────────────────────────────────────────────┘
8.3 Netty 服务器示例
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(
ChannelHandlerContext ctx, String msg) {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Echo: " + msg);
}
});
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(8080).sync();
System.out.println("Netty Server started on port 8080");
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
8.4 Netty 核心组件
| 组件 |
说明 |
EventLoopGroup |
事件循环组,包含多个 EventLoop |
EventLoop |
事件循环,一个线程处理多个 Channel 的 I/O |
Channel |
网络连接抽象(客户端/服务端) |
ChannelHandler |
处理 I/O 事件的处理器 |
ChannelPipeline |
Handler 链,Inbound/Outbound 双向传播 |
ChannelFuture |
异步操作结果的容器 |
ByteBuf |
比 NIO Buffer 更强大的缓冲区 |
Bootstrap / ServerBootstrap |
启动引导类 |
8.5 Netty vs 原生 NIO 对比
| 特性 |
原生 NIO |
Netty |
| 代码量 |
多(需手动管理) |
少(链式配置) |
| 拆包/粘包 |
手动实现 |
内置多种解码器 |
| 内存管理 |
手动 |
池化 ByteBuf,引用计数 |
| 异常处理 |
分散 |
统一 ChannelHandler |
| SSL/TLS |
复杂 |
SslHandler 一行添加 |
| 协议支持 |
仅 TCP/UDP |
HTTP, WebSocket, Redis, Redisson 等 |
| 社区生态 |
仅 JDK 文档 |
广泛使用,文档丰富 |
九、网络编程最佳实践
9.1 连接管理
socket.setSoTimeout(30_000);
try (Socket socket = new Socket(host, port)) {
}
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
9.2 线程池配置
ExecutorService ioPool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2);
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("network-worker-%d")
.setUncaughtExceptionHandler((t, e) ->
log.error("Thread {} error", t.getName(), e))
.build();
9.3 拆包/粘包处理
TCP 是面向字节流的协议,不保证消息边界,常见解决方案:
| 方案 |
说明 |
适用场景 |
| 固定长度 |
每个消息固定 N 字节 |
定长协议 |
| 分隔符 |
用特殊字符分隔(如 \n) |
文本协议 |
| 长度字段 |
消息头包含长度字段 |
二进制协议 |
| 消息结束标记 |
特定结束标记 |
自定义协议 |
public class LengthFrameDecoder {
public static byte[] decode(ByteBuffer buffer) {
if (buffer.remaining() < 4) return null;
int length = buffer.getInt();
if (buffer.remaining() < length) return null;
byte[] data = new byte[length];
buffer.get(data);
return data;
}
}
9.4 零拷贝技术
FileChannel fileChannel = FileChannel.open(
Paths.get("largefile.dat"), StandardOpenOption.READ);
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("host", 8080));
fileChannel.transferTo(0, fileChannel.size(), socketChannel);
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024);
CompositeByteBuf composite = Unpooled.compositeBuffer();
composite.addComponents(true, buf1, buf2, buf3);
9.5 性能调优清单
| 调优项 |
建议值 |
说明 |
SO_BACKLOG |
1024+ |
连接等待队列大小 |
SO_REUSEADDR |
true |
允许地址复用 |
TCP_NODELAY |
true |
禁用 Nagle 算法,减少延迟 |
SO_RCVBUF |
按需调整 |
接收缓冲区,高吞吐可增大 |
SO_SNDBUF |
按需调整 |
发送缓冲区 |
SO_KEEPALIVE |
true |
TCP 保活 |
reuseAddress |
true |
快速重启服务 |
| 连接池 |
必选 |
复用连接,减少握手开销 |
| 批处理写入 |
推荐 |
writeAndFlush 合并多次写 |
9.6 安全注意事项
- 输入校验:所有来自网络的输入都视为不可信
- 速率限制:防止单个客户端占用过多资源
- SSL/TLS:敏感数据必须加密传输
- 连接数限制:对单 IP 设置最大连接数,防止 DoS
- 超时设置:防止慢连接占用资源
- 日志脱敏:不在日志中记录敏感信息
9.7 常见问题排查
| 问题 |
可能原因 |
排查方法 |
Connection refused |
服务未启动或端口错误 |
netstat -tlnp 检查端口 |
Connection timeout |
网络不通或防火墙 |
telnet host port 测试连通性 |
Connection reset |
对端异常关闭 |
检查服务端日志 |
| CPU 100% |
NIO 空轮询 bug |
检查 JDK 版本,升级或打补丁 |
| 内存泄漏 |
连接未关闭 / 缓冲区未释放 |
netstat 检查连接数,Heap Dump 分析 |
| 消息不完整 |
拆包/粘包处理不当 |
添加协议边界处理 |
总结:Java 网络编程从 BIO 到 NIO 再到 AIO,经历了从阻塞到非阻塞再到异步的演进。实际生产中,推荐使用 NIO 配合 Netty 框架,既保证了高性能,又降低了开发复杂度。对于 HTTP 客户端场景,Java 11+ 的 HttpClient 是首选方案。
📊 本节统计
| 统计维度 |
数值 |
口径 |
| 分类主题数 |
1 |
网络编程(Socket / BIO / NIO / AIO / Reactor / HttpClient / Netty 9 节) |
| 子 README 数 |
0 |
无子 README |
| 含 frontmatter 的 README |
1 / 1 |
100% 覆盖(2026-07-01) |
统计时间戳:2026-07-01
← 返回 01.java 主模块