如何使用 Netty 实现 NIO 方式发送 HTTP 请求

如何使用 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()

总结

  1. Netty 通过 EventLoop + Pipeline + Handler 实现 NIO HTTP 客户端
  2. 非阻塞 I/O 带来 高并发、低延迟
  3. 代码 可直接拷贝 到生产环境,已包含异常、关闭、性能最佳实践

Happy Coding!

相关推荐
Code_Artist3 天前
一天之内我让 AI 用 Netty 造了一个最小可用的 MVC 框架:体验一下造轮子的快感😅!
后端·netty·ai编程
斯普润布特5 天前
物联网-Spring+Netty 框架整合
java·物联网·netty
笨手笨脚の5 天前
Netty 如何高效的使用内存
netty·堆外内存
佛祖让我来巡山9 天前
Netty保姆级全解析|技术背景+核心知识点+生产实战教程
netty
佛祖让我来巡山11 天前
Netty入门|从BIO到Netty:一步步看懂Java网络编程的迭代逻辑
netty·nio·bio
文慧的科技江湖18 天前
光伏储能充电系统PRD功能列表 - 慧知开源充电桩平台
开发语言·开源·netty·慧知开源充电桩平台·开源充电桩平台
不早睡不改名@1 个月前
Netty源码分析---Reactor线程模型深度解析(一)
java·笔记·学习·netty
zs宝来了1 个月前
Netty Reactor 模型:Boss、Worker 与 EventLoop
reactor·netty·源码解析·线程模型·eventloop
不早睡不改名@1 个月前
Netty源码分析---Reactor线程模型深度解析(二)
java·网络·笔记·学习·netty
不早睡不改名@1 个月前
Netty源码解析---FastThreadLocal-addToVariablesToRemove方法详解
java·网络·笔记·学习·netty