Netty源码分析-Channel分类

先来看下Channel相关类图:


Netty中channel类图

为了便于理解,上面的类图对层次关系做了一定的简化。
Channel接口定义了Netty中网络IO最顶层的框架。AbstractChannel是Channel接口的骨架实现,这个类中定义了channel的几个重要成员,id(ChannelId),unsafe(Unsafe),pipeline(DefaultChannelPipeline),eventLoop(EventLoop)。服务端channel(NioServerSocketChannel)和客户端channel(NioSocketChannel)都会逐层的调用父类构造函数,从而创建创建或绑定上述几个成员变量。AbstractNioChannel主要作用是负责Nio相关的部分,使用selector的方式监听读写事件。AbstractNioChannel有成员变量SelectionKey,成员变量SelectableChannel(保存底层jdk的channel),成员变量readInterestOp(OP_READ或OP_ACCEPT事件)。
AbstractNioChannel的构造函数:

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch; // 保存底层jdk的channel
        this.readInterestOp = readInterestOp; // 保存感兴趣的事件
        try {
            ch.configureBlocking(false); // 设置jdk的底层channel为非阻塞模式
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

NioSocketChannel和NioServerSocketChannel注册事件区别

接下来就是两大阵营服务端channel(AbstractNioMessageChannel,NioServerSocketChannel)和客户端channel(AbstractNioByteChannel,NioServerChannel)。它们都继承了AbstractNioChannel,说明它们都是通过selector轮询IO事件的,它们之间最大的区别是它们向selector注册的IO事件不同。

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT); // 服务端channel注册OP_ACCEPT事件
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
    public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket); // 调用 AbstractNioByteChannel 的构造函数
        config = new NioSocketChannelConfig(this, socket.socket());
    }
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ); // 客户端channel注册OP_READ事件,关心数据的读写
    }

NioSocketChannel和NioServerSocketChannel抽象读事件的区别

服务端channel和客户端channel的另一个区别是底层的Unsafe不同。Unsafe负责具体实现是客户端channel还是客户端channel的协议。服务端channel对应的是NioMessageUnsafe,客户端channel对应的是NioByteUnsafe(NioSocketChannelUnsafe继承自它)。
从源码中来分析客户端unsafe的创建:

    protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannelUnsafe();  // 客户端channel直接创建unsafe
    }

  // NioSocketChannelUnsafe 只有一个准备关闭的方法,大部分功能还是来自于 NioByteUnsafe
    private final class NioSocketChannelUnsafe extends NioByteUnsafe {
        @Override
        protected Executor prepareToClose() {
            try {
                if (javaChannel().isOpen() && config().getSoLinger() > 0) {
                    // We need to cancel this key of the channel so we may not end up in a eventloop spin
                    // because we try to read or write until the actual close happens which may be later due
                    // SO_LINGER handling.
                    // See https://github.com/netty/netty/issues/4449
                    doDeregister();
                    return GlobalEventExecutor.INSTANCE;
                }
            } catch (Throwable ignore) {
                // Ignore the error as the underlying channel may be closed in the meantime and so
                // getSoLinger() may produce an exception. In this case we just return null.
                // See https://github.com/netty/netty/issues/4449
            }
            return null;
        }
    }

服务端channel的unsafe,在AbstractNioMessageChannel中可以看到:

    protected AbstractNioUnsafe newUnsafe() {
        return new NioMessageUnsafe(); // 服务端channel创建unsafe
    }

服务端channel和客户端channel的第三个不同:读取的内容不同。服务端channel的读是读取一条新的连接;客户端channel的读是读取IO数据。
我们来看服务端channel读事件相关的源码,NioMessageUnsafe的read方法:

    private final class NioMessageUnsafe extends AbstractNioUnsafe {

        private final List<Object> readBuf = new ArrayList<Object>();

        @Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        // 最核心的功能doReadMessage,也就是读取一条连接
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }
    protected int doReadMessages(List<Object> buf) throws Exception {
        // 核心代码
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            logger.warn("Failed to create a new channel from an accepted socket.", t);

            try {
                ch.close();
            } catch (Throwable t2) {
                logger.warn("Failed to close a socket.", t2);
            }
        }

        return 0;
    }

  public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
                @Override
                public SocketChannel run() throws IOException {
                    // 服务端channel,accept客户端连接
                    return serverSocketChannel.accept();
                }
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getCause();
        }
    }

来看客户端channel的读事件,NioByteUnsafe的read方法:

        @Override
        public final void read() {
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    // 从这里看出,客户端channel是读字节
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

NioSocketChannel和NioServerSocketChannel绑定channelConfig的区别

最后一点,服务端channel和客户端channel绑定的channeConfig不同。


netty channelConfig.PNG

推荐阅读更多精彩内容

  • 公司自研rpc框架,底层网络通信框架为netty;作为it小白,有必要学习rpc框架及对应的系统底层网络通信框架。...
    未名枯草阅读 444评论 0 1
  • 1-netty源码分析之Server 看netty源码之后进行总结的第一篇笔记,无非帮助自己对于看代码的一个总结,...
    致虑阅读 541评论 0 5
  • 原文:https://wangwei.one/posts/netty-channel-source-analyse...
    wangwei_hz阅读 990评论 0 2
  • Channel介绍 Channel是JDK 的NIO类库中的重要组成部分,我们在之前的代码中也经常用到io.net...
    无聪帅阅读 509评论 0 0
  • 斯里兰卡✈️北京 6070km 将近9个小时的飞行 在香港机场购物 不自觉地用英语询问店员有没有中文导购用英语点餐...
    么么啵__阅读 160评论 0 0
  • 一人独醉清风阁, 两心畏惧杯中影。 静闻天下共鸣声, 试问几多实在人。
    李位阅读 63评论 0 0
  • 母亲去世已一个半月了。 有时候,还是恍惚觉得她没有走。只要回家就能看见她,躺在床上抓着我的手,摇几下,像个孩子一样...
    文心匠阅读 486评论 25 17
  • 作为今夏科技创新实力爆棚的旗舰级手机,OPPO Find X自发售以来频频售罄断货。在一部国产手机上感受到“为苹果...
    笔点酷玩阅读 531评论 4 0