如何在 Netty 中的 ssl 握手之前优雅地关闭频道?

Posted

技术标签:

【中文标题】如何在 Netty 中的 ssl 握手之前优雅地关闭频道?【英文标题】:How to close a channel gracefully before ssl handshake in Netty? 【发布时间】:2018-03-08 05:42:38 【问题描述】:

我在我的应用程序中添加了一个限制功能,当传入的请求率超过阈值时,它需要在 SSL 握手之前关闭一个通道以减少 CPU 使用率。现在我使用带有服务器模式的 Netty SslHandler 进行握手。我目前的实现是在SslHandler 之前添加ChannelInboundHandlerAdapter 并重写channelActive 方法:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception  
    if (!limiter.tryAcquire()) 
        ctx.close();
        return;
    

    ctx.pipeline().remove(this);
    ctx.fireChannelActive();

通过这种方式,通道可以在变为活动状态时关闭。但是,我会收到握手失败的SslHandshakeCompletionEvent。我阅读了 Netty SslHandler 的源代码。当 channelInactive 事件被触发时,它将在channelInactive 方法中设置握手失败。所以我想知道是否有更好的方法来关闭通道而不触发握手失败事件,因为当我限制关闭通道时握手过程还没有开始。

【问题讨论】:

【参考方案1】:

你在错误的地方解决问题。这个想法不是关闭被接受的通道太快,而是不要太快地接受它们,这将在接受处理程序中完成,而不是在通道处理程序中完成。

【讨论】:

你的意思是修改ServerSocketChannel?我有一个将 InboundHandler 添加到NioserverSocketChannel 的解决方案,因此我可以在传入通道注册和激活之前关闭它。但是,这意味着我必须使用channel.unsafe().closeForcibly()。如果我使用普通的channel.close() 方法,Netty 会抛出通道未注册异常。但是根据 Netty 文档Unsafe,这似乎是内部 API,我们不应该使用它吗? 我的意思是,当您拥有最大连接数时,要么停止接受连接,要么取消注册对 OP_ACCEPT 的兴趣,但是 Netty 会这样做。你不想关闭连接:已经太晚了;你也不需要关闭ServerSocketChannel。基本上就是不要打电话给accept() 我一开始也考虑过这个方案。但是,我正在添加一个速率限制功能,这意味着我需要每秒接受一些连接请求并拒绝其余的连接请求。如果只是让Netty 不接受新连接,那不是速率限制的目标。当然,我可以每秒修改OP_ACCEPT来控制Netty每秒接受的连接数,但是这样是不是一个好的解决方案? 所以你需要在每一秒内接受这么多的连接,然后在那一秒的剩余时间内停止接受它们。这完全符合您的目标。不清楚你为什么不这么认为, 我只是想知道以高频率模式修改OP_ACCEPT是否是一个好习惯(每秒修改两次),它会导致Netty的高开销吗?【参考方案2】:

您的方法几乎是正确的,但是,覆盖 channelRegistered 方法而不是 channelActive 是正确的。

@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception 
    if (isGlobalLimitReached()) 
        ctx.close();
     else 
        ctx.pipeline().remove(this);
        ctx.pipeline().addFirst(sslCtx.newHandler(ctx.alloc()));
        ctx.fireChannelRegistered();
     

此外,您需要在流水线的开头动态添加SslHandler,并且仅在满足您的条件时(未达到限制)。

在这种情况下,您不会收到SslHandshakeCompletionEvent。当SslHandler 添加到管道和 channel.isActive() return true(你的情况在上面的代码中)。

channelRegistered 是您可以拒绝/关闭连接的最近点。

【讨论】:

这让我很困惑。我使用调试模式来尝试找出SslHandler 何时开始握手。似乎具有客户端模式的处理程序在通道处于活动状态时开始握手。如果SslHandler 处于服务器模式,它会在通道读取时开始握手(SslHandler 源代码中有一个readDuringHandshake 标志)。最让我困惑的是,即使它在通道处于活动状态时开始握手,我的处理程序也是在SslHandler 之前添加的,而我的channelInactive 方法应该在握手之前运行并关闭通道。为什么它仍然给SslHandshakeCompletionEvent 或者就像你说的,当SslHandler被添加到管道时,握手就开始了,但它似乎立即开始,并且在通道变得活跃之前。 更新:修改方法为channelRegistered并测试,Netty还是给SslHandshakeCompletionEvent @JianZhu 我的错,我没有查看 channel.isActive() 方法。请检查我上面编辑的答案。 非常感谢。我尝试根据条件动态地将SslHandler 添加到管道中。如果条件匹配,我加SslHandler,否则不加。该解决方案适用于我的用例。只是仍然想知道为什么第一个解决方案不起作用。正如我在更新评论中所说,我尝试添加一个处理程序,该处理程序像您的答案中的代码一样重写channelRegistered 方法,并将其添加到 SslHandler 之前。我仍然会收到SslHandshakeCompletionEvent。这是否意味着握手已经开始,甚至在channelResigtered 事件被触发之前?

以上是关于如何在 Netty 中的 ssl 握手之前优雅地关闭频道?的主要内容,如果未能解决你的问题,请参考以下文章

移除后 Netty SslHandler 握手超时

Netty 和 SSL websocket 客户端

使用 2-way SSL Handskake(客户端和服务器证书)设置 Netty

《Netty进阶之路》目录

服务器建立sslConnection后如何在netty客户端上获取回调

在 SSL/TLS 握手中的“server-hello”消息期间选择证书链的逻辑?