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

相关推荐
尽兴-1 天前
RocketMQ核心源码深度解读:架构原理与核心机制剖析
架构·rocketmq·netty·架构原理·消息持久化
Javatutouhouduan2 天前
Netty进阶指南:基础+中级+高级+架构行业运用+源码分析
java·netty·java面试·网络io·后端开发·java程序员·互联网大厂
MrSYJ5 天前
Netty异常传播机制
java·服务器·netty
qq_232045577 天前
精积微半导体面试(部分)
netty·策略模式·nio·内存抖动·threadlocal·bitmap·复用
一叶飘零_sweeeet11 天前
从 BIO 到 AIO 全链路拆解:Reactor 模型演进与高并发 IO 架构落地实战
netty·nio
belhomme12 天前
(面试题)Netty 线程模型
java·面试·netty
怒放吧德德20 天前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
一个有梦有戏的人25 天前
Java 网络编程核心:BIO、NIO、AIO IO 模型深度解析与实战
java·网络·后端·netty·nio
怒放吧德德1 个月前
Java 网络编程核心:BIO、NIO、AIO IO 模型深度解析与实战
后端·netty
hrhcode1 个月前
【Netty】五.ByteBuf内存管理深度剖析
java·后端·spring·springboot·netty