Netty4学习笔记(9)-- Channel状态转换

9 篇文章 0 订阅
9 篇文章 9 订阅

前面有一篇文章分析过Bootstrap类如何引导NioSocketChannel上篇文章简单讨论了一下Channel接口的方法,知道有四个方法用来查询Channel的状态:isOpen()isRegistered()isActive()isWritable()。这篇文章结合Bootstrap分析一下前三个方法,看看NioSocketChannel是如何到达这三个状态的。

Channel继承层次图

分析上面提到的三个状态的时候,会去看Channel继承层次里某些类的代码,为了方便参考,我画了一张(不太严格的)UML类图,如下所示:

open状态

先从isOpen()方法入手,isOpen()方法是在AbstractNioChannel抽象类里实现的,下面是这个类的关键代码:

public abstract class AbstractNioChannel extends AbstractChannel {
    ...
    private final SelectableChannel ch;
    ...
    @Override
    public boolean isOpen() {
        return ch.isOpen();
    }
    ...
}
可以看出来,Netty的Channel是否open取决于Java的SelectableChannel是否open。换句话说,只要找出Netty何时open了这个SelectableChannel,就可以知道Channel何时到达了open状态。从Bootstrap的connect()方法开始顺藤摸瓜就能找出答案:

Bootstrap.connect(String inetHost, int inetPort)
  -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)
    -> AbstractBootstrap.initAndRegister()
      -> BootstrapChannelFactory.newChannel()
        -> NioSocketChannel()
          -> NioSocketChannel.newSocket()
            -> SocketChannel.open()
重点看看 initAndRegister()方法:

    // AbstractBootstrap.java
    final ChannelFuture initAndRegister() {
        final Channel channel = channelFactory().newChannel();
        try {
            init(channel);
        } catch (Throwable t) {
            channel.unsafe().closeForcibly();
            return channel.newFailedFuture(t);
        }

        ChannelPromise regPromise = channel.newPromise();
        group().register(channel, regPromise);
        ...
        return regPromise;
    }
initAndRegister()方法先创建了Channel实例(此时Channel已经处于open状态),然后把它注册到group里,所以大概能够知道,Channel是在open之后进入registered状态的,如下图所示:


registered状态

为了证明上面的猜测,我们从NioEventLoopGroup.register()方法接着看代码。NioEventLoopGroup并没有实现register()方法,真正的实现是在它的超类MultithreadEventLoopGroup里:

    // MultithreadEventLoopGroup.java
    @Override
    public ChannelFuture register(Channel channel, ChannelPromise promise) {
        return next().register(channel, promise);
    }
根据 这篇文章的介绍,next()方法返回的是一个 NioEventLoop,看代码后知道,register()方法是在NioEventLoop的超类, SingleThreadEventLoop里实现的:

    // SingleThreadEventLoop.java
    @Override
    public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
        ...
        channel.unsafe().register(this, promise);
        return promise;
    }
好吧,继续看代码,知道调用的是 AbstractChannel.AbstractUnsafe.register()方法,这个方法又调用了 AbstractUnsafe.register0()方法,在register0()方法里, registered字段被设置为true。而AbstractChannel的isRegistered()方法正好是通过这个字段来判断是否是registered状态:

    // AbstractChannel.java
    @Override
    public boolean isRegistered() {
        return registered;
    }
也就是说,上面的猜测是正确的,Channel先进入open状态,然后通过把自己注册到group进入registered状态。


active状态

还是先看看isActive()方法是如何实现的(在NioSocketChannel里):

    // NioSocketChannel.java
    @Override
    public boolean isActive() {
        SocketChannel ch = javaChannel();
        return ch.isOpen() && ch.isConnected();
    }
也就是说,NioSocketChannel的active状态取决于SocketChannel的状态。根据前面的分析知道,NioSocketChannel构造函数执行之后,SocketChannel已经处于open状态了,那么接下来就看SocketChannel的connect()方法是何时被调用的。回到Bootstrap类的doConnect()方法:
Bootstrap.connect(String inetHost, int inetPort)
  -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)
    -> AbstractBootstrap.initAndRegister()
       Bootstrap.doConnect0(...)
         -> Channel.connect(SocketAddress remoteAddress, ChannelPromise promise
doConnect()方法在initAndRegister()之后又调用了doConnect0()方法,doConnect0()方法调用了Channel的connect()方法。在AbstractChannel里有connect()方法的实现:

    // AbstractChannel.java
    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return pipeline.connect(remoteAddress, promise);
    }
也就是说,connect实际上是被当做事件交给pipeline去处理的,而且是个outbound事件,看DefaultChannelPipeline:

    // DefaultChannelPipeline.java
    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, promise);
    }
tail是DefaultChannelHandlerContext实例:
    // DefaultChannelHandlerContext.java
    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return connect(remoteAddress, null, promise);
    }

    @Override
    public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
        ...
        final DefaultChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeConnect(remoteAddress, localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeConnect(remoteAddress, localAddress, promise);
                }
            }, promise, null);
        }

        return promise;
    }

    private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }
三个参数版的connect()方法看起来很复杂,但无非就是做了两件事:先沿着pipeline往前找到第一个outbound类型的context,接着调用这个context的invokeConnect()方法。然后context又调用了handler的connect()方法,而pipeline里必定会有一个outbound类型的context/handler,这个context就是head,相应的handler是内部类 HeadHandler

// DefaultChannelPipeline.java
static final class HeadHandler implements ChannelOutboundHandler {
    protected final Unsafe unsafe;

    protected HeadHandler(Unsafe unsafe) {
        this.unsafe = unsafe;
    }
    ...
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
        unsafe.connect(remoteAddress, localAddress, promise);
    }
    ...
}
HeadHandler只是调用了unsafe的connect()方法,unsafe是在构造函数里传进来的:

public DefaultChannelPipeline(AbstractChannel channel) {
    ...
    HeadHandler headHandler = new HeadHandler(channel.unsafe());
    head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);
    ...
}
Unsafe.connect()方法在AbstractNioChannel.AbstractNioUnsafe里实现,这个实现调用了AbstractNioChannel.doConnect()方法。doConnect()方法最终在NioSocketChannel里得以实现:

    // NioSocketChannel.java
    @Override
    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            javaChannel().socket().bind(localAddress);
        }

        boolean success = false;
        try {
            boolean connected = javaChannel().connect(remoteAddress);
            if (!connected) {
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

结论

代码分析的很复杂,但结论很简单:被Bootstrap引导的NioSocketChannel在构造好之后就进入了open状态,之后通过把自己注册进EventLoop进入registered状态,接着连接服务器进入active状态。




### 回答1: Netty-WebSocket-Spring-Boot-Starter是一个用于将Websocket集成到Spring Boot应用程序中的库。它使用Netty作为底层框架,提供了一种快速和可靠的方式来处理异步通信。 这个库提供了一种简单的方法来创建Websocket端点,只需要使用注释和POJO类即可。在这些端点上可以添加动态的事件处理程序,以处理连接、断开连接和消息事件等。 此外,Netty-WebSocket-Spring-Boot-Starter还包括了一些安全性的特性,如基于令牌的授权和XSS保护,可以帮助您保持您的Websocket应用程序安全。 总的来说,Netty-WebSocket-Spring-Boot-Starter提供了一种快速和易于使用的方式来构建Websocket应用程序,使得它成为应用程序开发人员的有用工具。 ### 回答2: netty-websocket-spring-boot-starter 是一个开源的 Java Web 开发工具包,主要基于 Netty 框架实现了 WebSocket 协议的支持,同时集成了 Spring Boot 框架,使得开发者可以更加方便地搭建 WebSocket 服务器。 该工具包提供了 WebSocketServer 配置类,通过在 Spring Boot 的启动配置类中调用 WebSocketServer 配置类,即可启动 WebSocket 服务器。同时,该工具包还提供了多种配置参数,如端口号、URI 路径、SSL 配置、认证配置等等,可以根据业务需求进行自定义配置。 此外,该工具包还提供了一些可扩展的接口和抽象类,如 WebSocketHandler、ChannelHandlerAdapter 等,可以通过继承和实现这些接口和抽象类来实现业务逻辑的处理和拓展。 总的来说,netty-websocket-spring-boot-starter 提供了一个高效、简单、易用的 WebSocket 服务器开发框架,可以减少开发者的开发成本和工作量。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值