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

相关推荐
9527出列11 小时前
Netty源码分析(六)--关于ChannelPipeline
netty·源码阅读
Luo_xguan2 天前
一、Netty-高并发IO底层原理(5种主要的IO模型)
java·服务器·netty·nio
戮戮11 天前
一次深入排查:Spring Cloud Gateway TCP 连接复用导致 K8s 负载均衡失效
tcp/ip·spring cloud·kubernetes·gateway·负载均衡·netty
fat house cat_11 天前
【netty】基于主从Reactor多线程模型|如何解决粘包拆包问题|零拷贝
java·服务器·网络·netty
Moe48814 天前
Netty技术:SimpleChannelInboundHandler<>的使用
netty
poemyang16 天前
jemalloc思想的极致演绎:深度解构Netty内存池的精妙设计与实现
rpc·netty
poemyang17 天前
“化零为整”的智慧:内存池如何绕过系统调用和GC,构建性能的护城河
rpc·netty
晓牛开发者19 天前
Netty4 TLS单向安全加密传输案例
netty
hanxiaozhang201820 天前
Netty面试重点-2
面试·netty
9527出列21 天前
Netty源码分析--客户端连接接入流程解析
网络协议·netty