写点什么

netty 系列之: 手持 framecodec 神器, 创建多路复用 http2 客户端

作者:程序那些事
  • 2021 年 12 月 05 日
  • 本文字数:3017 字

    阅读完需:约 10 分钟

netty系列之:手持framecodec神器,创建多路复用http2客户端

简介

在之前的文章中,我们实现了支持 http2 的 netty 服务器,并且使用支持 http2 的浏览器成功的进行访问。虽然浏览器非常通用,但是有时候我们也需要使用特定的 netty 客户端去和服务器进行通信。


今天我们来探讨一下 netty 客户端对 http2 的支持。

配置 SslContext

虽然 http2 并不强制要求支持 TLS,但是现代浏览器都是需要在 TLS 的环境中开启 http2,所以对于客户端来说,同样需要配置好支持 http2 的 SslContext。客户端和服务器端配置 SslContext 的内容没有太大的区别,唯一的区别就是需要调用 SslContextBuilder.forClient()而不是 forServer()方法来获取 SslContextBuilder,创建 SslContext 的代码如下:


SslProvider provider =                    SslProvider.isAlpnSupported(SslProvider.OPENSSL)? SslProvider.OPENSSL : SslProvider.JDK;            sslCtx = SslContextBuilder.forClient()                  .sslProvider(provider)                  .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)                  // 因为我们的证书是自生成的,所以需要信任放行                  .trustManager(InsecureTrustManagerFactory.INSTANCE)                  .applicationProtocolConfig(new ApplicationProtocolConfig(                          Protocol.ALPN,                          SelectorFailureBehavior.NO_ADVERTISE,                          SelectedListenerFailureBehavior.ACCEPT,                          ApplicationProtocolNames.HTTP_2,                          ApplicationProtocolNames.HTTP_1_1))                  .build();
复制代码


如果使用 SSL,那么 ssl handler 必须是 pipline 中的第一个 handler,所以将 SslContext 加入到 pipline 中的代码如下:


ch.pipeline().addFirst(sslCtx.newHandler(ch.alloc()));
复制代码

客户端的 handler

使用 Http2FrameCodec

netty 的 channel 默认只能接收 ByteBuf 消息,对于 http2 来说,底层传输的是一个个的 frame,直接操作底层的 frame 对于普通程序员来说并不是特别友好,所以 netty 提供了一个 Http2FrameCodec 来对底层的 http2 frame 进行封装成 Http2Frame 对象,方便程序的处理。


在服务器端我们使用 Http2FrameCodecBuilder.forServer()来创建 Http2FrameCodec,在客户端我们使用 Http2FrameCodecBuilder.forClient()来创建 Http2FrameCodec:


Http2FrameCodec http2FrameCodec = Http2FrameCodecBuilder.forClient()            .initialSettings(Http2Settings.defaultSettings())            .build();
复制代码


然后将其加入到 pipline 中即可使用:


        ch.pipeline().addLast(http2FrameCodec);
复制代码

Http2MultiplexHandler 和 Http2MultiplexCodec

我们知道对于 http2 来说一个 TCP 连接中可以创建多个 stream,每个 stream 又是由多个 frame 来组成的。考虑到多路复用的情况,netty 可以为每一个 stream 创建一个单独的 channel,对于新创建的每个 channel 来说,都可以使用 netty 的 ChannelInboundHandler 来对 channel 的消息进行处理,从而提升 netty 处理 http2 的效率。


而这个对 stream 创建新 channel 的支持,在 netty 中有两个专门的类,他们是 Http2MultiplexHandler 和 Http2MultiplexCodec。


他们的功能是一样的,Http2MultiplexHandler 继承自 Http2ChannelDuplexHandler,它必须和 Http2FrameCodec 一起使用。而 Http2MultiplexCodec 本身就是继承自 Http2FrameCodec,已经结合了 Http2FrameCodec 的功能。


public final class Http2MultiplexHandler extends Http2ChannelDuplexHandler
@Deprecatedpublic class Http2MultiplexCodec extends Http2FrameCodec
复制代码


但是通过检查源代码,我们发现 Http2MultiplexCodec 是不推荐使用的 API,所以这里我们主要介绍 Http2MultiplexHandler。


对于 Http2MultiplexHandler 来说,每次新创建一个 stream,都会创建一个新的对应的 channel,应用程序使用这个新创建的 channel 来发送和接收 Http2StreamFrame。


新创建的子 channel 会被注册到 netty 的 EventLoop 中,所以对于一个有效的子 channel 来说,并不是立刻就会被匹配到 HTTP/2 stream 上去,而是当第一个 Http2HeadersFrame 成功被发送或者接收之后,才会触发 Event 事件,进而进行绑定操作。


因为是子 channel,所以对于 connection level 的事件,比如 Http2SettingsFrame 和 Http2GoAwayFrame 会首先被父 channel 进行处理,然后再广播到子 channel 中进行处理。


同时,虽然 Http2GoAwayFrame 和 Http2ResetFrame 表示远程节点已经不再接收新的 frame 了,但是因为 channel 本身还可能有 queue 的消息,所以需要等待 Channel.read()为空之后,才会进行关闭操作。


另外对于子 channel 来说,因为不能知道 connection-level 流控制 window,所以如果有溢出的消息会被缓存在父 channel 的 buff 中。


有了 Http2MultiplexHandler,将其加入 client 的 pipline 就可以让客户端支持多路的 channel 了:


ch.pipeline().addLast(new Http2MultiplexHandler(new SimpleChannelInboundHandler() {            @Override            protected void channelRead0(ChannelHandlerContext ctx, Object msg) {                // 处理inbound streams                log.info("Http2MultiplexHandler接收到消息: {}",msg);            }        }))
复制代码

使用子 channel 发送消息

从上面的介绍我们知道,一旦使用了 Http2MultiplexHandler,那么具体的消息处理就是在子 channel 中了。那么怎么才能从父 channel 中获取子 channel,然后使用子 channel 来发送信息呢?


netty 提供 Http2StreamChannelBootstrap 类,它提供了 open 方法,来创建子 channel:


        final Http2StreamChannel streamChannel;        try {            if (ctx.handler() instanceof Http2MultiplexCodec) {                streamChannel = ((Http2MultiplexCodec) ctx.handler()).newOutboundStream();            } else {                streamChannel = ((Http2MultiplexHandler) ctx.handler()).newOutboundStream();            }
复制代码


我们要做的就是调用这个方法,来创建子 channel:


final Http2StreamChannel streamChannel = streamChannelBootstrap.open().syncUninterruptibly().getNow();
复制代码


然后将自定义的,专门处理 Http2StreamFrame 的 Http2ClientStreamFrameHandler,添加到子 channel 的 pipline 中即可:


final Http2ClientStreamFrameHandler streamFrameResponseHandler =                    new Http2ClientStreamFrameHandler();streamChannel.pipeline().addLast(streamFrameResponseHandler);
复制代码


准备完毕,构建 http2 消息,使用 streamChannel 进行发送:


// 发送HTTP2 get请求            final DefaultHttp2Headers headers = new DefaultHttp2Headers();            headers.method("GET");            headers.path(PATH);            headers.scheme(SSL? "https" : "http");            Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, true);            streamChannel.writeAndFlush(headersFrame);
复制代码

总结

以上就是使用 netty 的 framecode 构建 http2 的客户端和服务器端进行通信的基本操作了。


本文的例子可以参考:learn-netty4


本文已收录于 http://www.flydean.com/32-netty-http2client-framecodec/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 2021 年 12 月 05 日阅读数: 10
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:手持framecodec神器,创建多路复用http2客户端