【Netty专题】Netty源码剖析_Channel通道

首先我们来思考一个问题,什么是Channel?

Channel是Netty抽象出来的对网络I/O进行读/写的相关接口,与NIO中的Channel接口类似。

那么Channel有哪些主要功能?

  • 网络I/O的读/写
  • 客户端发起连接
  • 主动关闭连接、关闭链路
  • 获取通信双方的地址

说明:

Netty支持除了TCP以外的多种协议。不同协议、不同阻塞类型的连接会有所不同的Channel类型与之对应。

下面我们来看一下常见的几种Channel:

1. AbstractChannel

  • 首先他有几个主要属性:

EventLoop:每个Channel对应一条EventLoop线程。
DefaultChannelPipeline:一个Handler的容器,也可以将其理解为一个Handler链。Handler主要处理数据的编/解码和业务逻辑。
Unsafe:实现具体的连接与读/写数据,如网络的读/写、链路关闭、发起连接等。命名为Unsafe表示不对外提供使用,并非不安全。

我们来看一下AbstractChannel的功能图:
在这里插入图片描述
接下来我们来看源码:

  • 先看一些全局变量:
 private final Channel parent;
    private final ChannelId id;
    // 实现具体的连接读/写数据,如网络的读/写、链路关闭、发起连接等,unsafe表示不对外提供使用,并非不安全
    private final Unsafe unsafe;
    // handler调用链
    private final DefaultChannelPipeline pipeline;
    private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
    private final CloseFuture closeFuture = new CloseFuture(this);

    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    // 每个channel对应一个eventloop线程
    private volatile EventLoop eventLoop;
    private volatile boolean registered;
    private boolean closeInitiated;
    private Throwable initialCloseCause;
  • AbstractChannel中有一个子类AbstractUnsafe,这个类大量采用了“模板设计模式”,具体的实现由子类完成,例如其中的bind()方法:
@Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
   
            assertEventLoop();

            if (!promise.setUncancellable() || !ensureOpen(promise)) {
   
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
   
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
   
                // 模板设计模式,调用子类NioServerSocketChannel的doBind()方法
                doBind(localAddress);
            } catch (Throwable t) {
   
                // 绑定失败回调
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            // 从非活跃状态到活跃状态触发了active事件
            if (!wasActive && isActive()) {
   
                invokeLater(new Runnable() {
   
                    @Override
                    public void run() {
   
                        pipeline.fireChannelActive();
                    }
                });
            }

            // 绑定成功回调通知
            safeSetSuccess(promise);
        }

2. AbstractNioChannel

AbstractNioChannel 继承了 AbstractChannel,不过在其基础上又增加了一些属性和方法。

// 真正用到的NIO channel
    private final SelectableChannel ch;
    // 监听感兴趣的事件
    protected final int readInterestOp;
    // 注册到Selector后获取Key
    volatile SelectionKey selectionKey;

我们来看其中的一个主要方法,doRegister():

 /**
     * 在AbstractUnsafe的register0()方法中调用
     * @throws Exception
     */
    @Override
    protected void doRegister() throws Exception {
   
        boolean selected = false;
        for (;;) {
   
            try {
   
                /**
                 * 通过javaChannel()方法获取具体的Nio Channel
                 * 把Channel注册到其EventLoop线程的Selector上
                 * 对于注册后返回的selectionKey,需要为其设置Channel感兴趣的事件
                 */
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
   
                if (!selected) {
   
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    // 由于尚未调用select.select(...)
                    // 因此可能仍在缓存而未删除但已取消selectionKey
                    // 强制调用 selector.selectNow方法
                    // 将已经取消的selectionKey 从Selector上删除
                    eventLoop().selectNow();
                    selected = true;
                } else {
   
                    // 只有第一次跑出此异常, 才能调用selector.selectorNow进行取消
                    // 如果调用selector.selectNow 还有取消的缓存,则可能是JDK的一个bug
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

在AbstractNioChannel中有个非常重要的类——AbstractNioUnsafe,它继承了AbstractUnsafe,并且实现了其中的connect,flush0()等方法。
我们来探究一下connect方法:

@Override
        public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
   
            // 设置任务为不可取消状态,并确定channel已打开
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
   
                return;
            }

            try {
   
                // 确保没有正在进行的连接
                if (connectPromise != null) {
   
                    // Already a connect in process.
                    throw new ConnectionPendingException();
                }
                // 获取之前的状态
                boolean wasActive = isActive();
                /**
                 * 在远程连接时,会出现以下三种结果。
                 * 1. 连接成功,返回true
                 * 2. 暂时没有连接上,服务端没有返回ACK应答,连接结果不确定,返回false
                 * 3. 连接失败,直接抛出I/O异常
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值