Netty源码分析之NioSocketChannel源码走读

开篇词

我希望大家能够带着问题来学习本篇文章,在这里我给大家提出两个问题。第一个Netty是在哪里检测到有新连接接入的。第二个新连接是怎样注册到Nioeventlooop线程当中的。

netty新连接接入处理逻辑:

检测新连接接入,是通过serverchannnel绑定的selector轮询到accept事件,基于jdk的channel创建出netty的NioSocketChannel。接着netty给客户端channel分配一个Nioeventloop,并且把channel注册到该Nioeventloop对应的selector上,然后我们就可以向这个channel对应的selector注册读事件。

源码分析

NioEventloop.java
// 处理新连接接入的逻辑:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                // If the channel implementation throws an exception because there is no event loop, we ignore this
                // because we are only trying to determine if ch is registered to this event loop and thus has authority
                // to close ch.
                return;
            }
            // Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop
            // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
            // still healthy and should not be closed.
            // See https://github.com/netty/netty/issues/5125
            if (eventLoop != this || eventLoop == null) {
                return;
            }
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            int readyOps = k.readyOps();
            // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
            // the NIO JDK channel implementation may throw a NotYetConnectedException.
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                // See https://github.com/netty/netty/issues/924
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }

            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            这里是重点,进入此方法会执行到这个逻辑,判断是否有新连接过来,如果发现有op_accept
            事件,就会执行下面的unsafe.read();
                unsafe.read();
                if (!ch.isOpen()) {
                    // Connection already closed - no need to handle write.
                    return;
                }
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

接下来进入unsafe.read() 方法:

AbstractNioMessageChannel.java
 内部类NioMessageUnsafe
     @Override
        public void read() {
        // 判断当前是否在server的Nioeventloop中,否则报错
            assert eventLoop().inEventLoop();
            // server端的config
            final ChannelConfig config = config();
            // server端的pipeline
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                       这里的doReadMessages是重点,这里会创建jdk底层的channel,
                       然后封装成netty的niochannel,将读到的连接放在readBuf这个临时容器中。
                       下面我们来跟进这个doReadMessages(readBuf)
                   
                        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());
                    }
                }
            } 
NioServerSocketChannel.java
 @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
       // 创建jdk底层的channel
        SocketChannel ch = javaChannel().accept();

        try {
            if (ch != null) {
            // 进行封装成NioSocketChannel,然后添加到临时容器当中,同时构造方法中还传递了服务端channel
                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;
    }

所以在这里我们回答下第一个问题,是在哪里检测到新连接的,在Nioeventloop的processSelectedKey()处理io事件,当发现是accept事件后,就会调用unsafe.read()方法去创建niosocketchannel,这个channel是对jdk底层channel的封装。

NioSocketChannel的创建

NioSocketChannel构造方法

    public NioSocketChannel(Channel parent, SocketChannel socket) {
       // 代用父类AbstractNioByteChannel的构造方法
        super(parent, socket);
        // 创建config配置类,在里面会设置tcpnodelay为true,即小的数据包也会发出去,以降低延迟。
        config = new NioSocketChannelConfig(this, socket.socket());
    }
    AbstractNioByteChannel.java
        protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        // SelectionKey.OP_READ表示后续将此channel绑定到selector上时,表示我对读事件
       // 感兴趣,如果发生读事件的话,请交给我
        super(parent, ch, SelectionKey.OP_READ);
    }
    继续跟进去
    AbstractNioChannel.java
        protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        // 保存感兴趣事件标识
        this.readInterestOp = readInterestOp;
        try {
        // 设置io模式为非阻塞模式
            ch.configureBlocking(false);
        } 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);
        }
    }
    继续跟进supr(parent)方法
    AbstractChannel.java
        protected AbstractChannel(Channel parent) {
        // 创建这三个基本的组件
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

重点 channel的分类

1:NioServerSocketChannel
2:NioSocketChannel
3:Unsafe:实现每种channel底层的协议
下面是channel的层级关系图
在这里插入图片描述channel接口:定义网络读写最顶层的框架
AbstractChannel:对Channel的实现,所有channel功能抽象都可以这个抽象类来实现

可以看到,usafe id,pipeline,eventloop都是在这个类中进行抽象保存的
    private final Channel parent;
    private final ChannelId id;
    private final Unsafe unsafe;
    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;
    private volatile EventLoop eventLoop;
    private volatile boolean registered;

    /** Cache for the string representation of this channel */
    private boolean strValActive;

AbstractNiochannel:负责nio相关部分,主要使用selector的方式来进行读写事件的监听。

不管是服务端channel还是客户端channel,注册到selector上时都会有一个selectionkey,就是保存在这个类的成员变量里面
SelectableChannel ch保存了服务端channel或者客户端channel底层的jdk channel
    private final SelectableChannel ch;
    这是一个读或accept事件
    protected final int readInterestOp;
    volatile SelectionKey selectionKey;
    boolean readPending;

客户端channel:AbstractNioByteChannel,NioSocketchannel,NioByteUnsafe(是AbstractNioByteChannel)的一个成员变量
服务端channel:AbstractNioMessageChannel,NioServerSocketchannel,NioMessageUnsafe(是AbstractNioMessageChannel的一个成员变量)
而AbstractNioByteChannel与AbstractNioMessageChannel都继承AbstractNiochannel,说明都是通过selector方式去轮询io事件的,只不过关心事件不同。如下所示

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

unsafe:实现每种channel的读写抽象,对于读客户端channel的读是读取数据,而服务端channel的读是读一个连接。
服务端的读:
在这里插入图片描述客户端的读:读的是io数据
在这里插入图片描述

新连接建立如何分配Eventloop和注册selector

这里有很重要的一点:在创建服务端的channel时,如果有新连接过来,那么就会调用unsafe.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 {
                    //将读到的连接放入临时容器readbuf中
                        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从head节点向下传播连接,这里地fireChannelread方法因为传递的是
                    // 连接,所以跟进去最后调用channelread()方法接口,最终会调用到
                    //  ServerBootstrap类的内部类ServerBootstrapAcceptor的channelread方法
                    // 注意ServerBootstrapAcceptor在ServerBootstrap.init()方法中创建
                    //下面具体分析channelread()方法实现
                    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();
                }
            }
        }
    }
    }
  @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            for (Entry<ChannelOption<?>, Object> e: childOptions) {
                try {
                    if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
                        logger.warn("Unknown channel option: " + e);
                    }
                } catch (Throwable t) {
                    logger.warn("Failed to set a channel option: " + child, t);
                }
            }

            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

            try {
                childGroup.register(child).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
                forceClose(child, t);
            }
        }

在这里主要做了三件事情 (childhandler与options与attrs都是我们用户自定义的代码)
在这里插入图片描述到这里我们先梳理一下逻辑:
首先启动Nioeventloop,然后执行其中的processSelectedKey方法,在其中判断是否有新连接接入事件,若有则调用unsave.read(),在其中创建新连接,然后调用pipeline.firechannelread(readBuf.get(i)),因为是一个连接,所以最终会调用到ServerBootstrap.ServerBootstrapAcceptor的channelread方法。所做的事情就是上图所示要做的。
接下来我们就来聊一聊Nioeventloop。
在这里插入图片描述NioSocketChannel读事件的注册

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值