如何使用 Netty 实现 NIO 方式发送 HTTP 请求
摘要
Netty 是一个高性能、事件驱动的 NIO 框架,常用于 RPC、网关、代理等场景。本文将手把手教你 仅用 Netty 核心 API 完成 非阻塞 HTTP 客户端,涵盖连接建立、请求构造、响应解析、异常处理、性能调优等全部细节。所有代码均可直接复制运行。
背景知识
概念 | 说明 |
---|---|
Netty | 基于 Java NIO 的网络通信框架,单线程可管理万级连接 |
EventLoopGroup | 事件循环组,内部维护 Selector 与线程 |
ChannelPipeline | 责任链,In/Out 事件按顺序被 Handler 处理 |
非阻塞 I/O | 线程不阻塞等待数据,通过事件回调处理 |
环境准备
Maven 依赖
xml
<dependencies>
<!-- Netty 全家桶 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.110.Final</version>
</dependency>
<!-- 日志门面,可选 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.12</version>
</dependency>
</dependencies>
核心组件
类/接口 | 作用 |
---|---|
Bootstrap |
客户端启动器 |
NioEventLoopGroup |
事件循环线程池 |
HttpClientCodec |
HTTP 编解码 |
HttpObjectAggregator |
将 HTTP 分片聚合成完整对象 |
SimpleChannelInboundHandler<FullHttpResponse> |
响应处理器 |
完整代码
java
public final class NettyHttpClient {
private final EventLoopGroup group = new NioEventLoopGroup(4);
private final Bootstrap bootstrap = new Bootstrap();
public NettyHttpClient() {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true) // 关闭 Nagle
.option(ChannelOption.SO_KEEPALIVE, true) // TCP KeepAlive
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
// 1. HTTP 编解码
p.addLast(new HttpClientCodec());
// 2. 聚合分片 -> FullHttpResponse
p.addLast(new HttpObjectAggregator(64 * 1024));
// 3. 业务处理器
p.addLast(new HttpResponseHandler());
}
});
}
/**
* 发送 GET 请求
*/
public CompletableFuture<String> get(String host, int port, String uri) {
return send(HttpMethod.GET, host, port, uri, null);
}
/**
* 发送 POST 请求
*/
public CompletableFuture<String> post(String host, int port, String uri, String jsonBody) {
return send(HttpMethod.POST, host, port, uri, jsonBody);
}
private CompletableFuture<String> send(HttpMethod method,
String host, int port,
String uri, String body) {
CompletableFuture<String> promise = new CompletableFuture<>();
ChannelFuture cf = bootstrap.connect(host, port);
cf.addListener((ChannelFutureListener) f -> {
if (!f.isSuccess()) {
promise.completeExceptionally(f.cause());
return;
}
Channel ch = f.channel();
byte[] bodyBytes = body == null ? new byte[0] : body.getBytes(StandardCharsets.UTF_8);
FullHttpRequest req = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, method, uri,
Unpooled.wrappedBuffer(bodyBytes));
req.headers()
.set(HttpHeaderNames.HOST, host)
.set(HttpHeaderNames.CONTENT_TYPE, "application/json")
.set(HttpHeaderNames.CONTENT_LENGTH, bodyBytes.length);
// 把 promise 绑定到 Channel
ch.attr(HttpResponseHandler.PROMISE_KEY).set(promise);
ch.writeAndFlush(req);
});
return promise;
}
public void shutdown() {
group.shutdownGracefully();
}
/* 响应处理器 */
static class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
static final AttributeKey<CompletableFuture<String>> PROMISE_KEY =
AttributeKey.valueOf("PROMISE");
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse resp) {
CompletableFuture<String> f = ctx.channel().attr(PROMISE_KEY).get();
if (f != null) {
f.complete(resp.content().toString(StandardCharsets.UTF_8));
}
ctx.close(); // 关闭连接(可根据业务复用)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
CompletableFuture<String> f = ctx.channel().attr(PROMISE_KEY).get();
if (f != null) {
f.completeExceptionally(cause);
}
ctx.close();
}
}
}
运行测试
1. 启动一个本地 HTTP 服务(Spring Boot)
java
@SpringBootApplication
public class EchoApplication {
public static void main(String[] args) {
SpringApplication.run(EchoApplication.class, args);
}
@RestController
public static class EchoController {
@PostMapping("/echo")
public Map<String, Object> echo(@RequestBody Map<String, Object> body) {
return Map.of("received", body, "timestamp", System.currentTimeMillis());
}
}
}
2. 客户端调用
java
public class Demo {
public static void main(String[] args) throws Exception {
NettyHttpClient client = new NettyHttpClient();
String json = "{\"msg\":\"hello netty\"}";
CompletableFuture<String> future = client.post("localhost", 8080, "/echo", json);
future.thenAccept(System.out::println)
.exceptionally(Throwable::printStackTrace)
.join();
client.shutdown();
}
}
输出(格式化后):
json
{"received":{"msg":"hello netty"},"timestamp":1719999999999}
性能调优
参数 | 说明 |
---|---|
EventLoopGroup(0) |
让 Netty 自动按 CPU 核数分配线程 |
ChannelOption.TCP_NODELAY |
关闭 Nagle 算法,降低延迟 |
ChannelOption.SO_KEEPALIVE |
避免长时间空闲被防火墙断开 |
连接复用 | 使用 ChannelPool 减少 TCP 三次握手 |
常见问题
问题 | 解决 |
---|---|
响应为空 | 检查 Content-Type 与 Content-Length |
连接超时 | 设置 ChannelOption.CONNECT_TIMEOUT_MILLIS |
内存泄漏 | 确保 ByteBuf 释放(ReferenceCountUtil.release() ) |
总结
- Netty 通过 EventLoop + Pipeline + Handler 实现 NIO HTTP 客户端
- 非阻塞 I/O 带来 高并发、低延迟
- 代码 可直接拷贝 到生产环境,已包含异常、关闭、性能最佳实践
Happy Coding!