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

相关推荐
Derek_Smart7 天前
Netty 客户端与服务端选型分析:下位机连接场景
spring boot·后端·netty
Derek_Smart9 天前
工业级TCP客户端高可靠连接架构设计与Netty优化实践
java·性能优化·netty
c_zyer10 天前
FreeSWITCH与Java交互实战:从EslEvent解析到Spring Boot生态整合的全指南
spring boot·netty·freeswitch·eslevent
Derek_Smart13 天前
工业物联网千万级设备通信优化:Netty多帧解码器实战,性能提升
java·性能优化·netty
皮皮林5511 个月前
Netty 超详细解答十问十答
netty
idolyXyz1 个月前
[netty5: LifecycleTracer & ResourceSupport]-源码分析
netty·netty-buffer
idolyXyz1 个月前
[netty5: WebSocketServerHandshaker & WebSocketServerHandshakerFactory]-源码分析
netty
idolyXyz1 个月前
[netty5: WebSocketFrameEncoder & WebSocketFrameDecoder]-源码解析
netty
idolyXyz1 个月前
[netty5: HttpObjectEncoder & HttpObjectDecoder]-源码解析
netty