Netty 客户端连接源码解析

当服务端启动后,那么久可以做客户端连接的事情了。客户端的示例代码在Netty服务端启动源码解析那篇文章中已经有展示了,那么这里在回顾一下,代码如下:

public class NettyClient {
    public static void main(String[] args) throws Exception {

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 使用NioSocketChannel作为客户端的通道实现
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //加入处理器
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("netty client start。。");
            //启动客户端去连接服务器端
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();
            //对通道关闭进行监听
            cf.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

在上面示例代码中的bootstrap.connect("127.0.0.1", 9000).sync()这行代码的作用就是去连接服务器的请求,当客户端的连接请求过来时,那么久可以开始处理连接请求了。

连接请求概览

关于处理客户端连接请求的方法,在Netty服务端启动源码解析那篇文章中所展示的那张Netty Reactor模型图中是有提到的,这里我们就不再展示了,在服务器注册完连接请求事件后,便具备了处理请求事件的能力,那张Netty Reactor模型图中可以看出当有事件来时,便会调用select方法来进行处理,当处理完之后便会调用io.netty.channel.nio.NioEventLoop#processSelectedKeys方法,来将客户端的SocketChannel注册到Selector上。这里我们看一下构件连接的核心时序图,如下:

 

上图对构建连接的程序链路,进行了更加细粒度的拆分,接下来的源码分析的大体逻辑,便是按照上图进行分析。

(回顾)注册事件

在讲Netty服务启动源码解析的时候,在讲到创建Channel的时候,便是调用channelFactory.newChannel()方法进行创建的,而在这个方法中所调用的便是当时在Netty服务端示例源码中调用io.netty.bootstrap.AbstractBootstrap#channel方法是传入的NioServerSocketChannel这个Class的无参构造函数。这里我们就不回顾channelFactory.newChannel()方法是怎么实现的了,只看下NioServerSocketChannel类的无参构造函数的代码,其实在客户端的逻辑是一样的,客户端NioSocketChannel创建代码如下:

public NioSocketChannel() {
    this(DEFAULT_SELECTOR_PROVIDER);
}

public NioSocketChannel(SelectorProvider provider) {
    this(newSocket(provider));
}

public NioSocketChannel(SocketChannel socket) {
    this(null, socket);
}

public NioSocketChannel(Channel parent, SocketChannel socket) {
    // SelectionKey.OP_ACCEPT:连接事件
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
    super(parent, ch, SelectionKey.OP_READ);
}

当创建完channel后,就可以通过io.netty.channel.AbstractChannel.AbstractUnsafe#register(channel)来注册Channel了,因为初次启动eventLoop.inEventLoop()为false的原因,那么就是执行eventLoop.execute(...)方法了,这里便来回顾一下这段代码逻辑。代码如下:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    if (eventLoop == null) {
        throw new NullPointerException("eventLoop");
    } else if (AbstractChannel.this.isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
    } else if (!AbstractChannel.this.isCompatible(eventLoop)) {
        promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
    } else {
        AbstractChannel.this.eventLoop = eventLoop;
        // 第一次为false
        if (eventLoop.inEventLoop()) {
            this.register0(promise);
        } else {
            try {
                eventLoop.execute(new Runnable() {
                    public void run() {
                        AbstractUnsafe.this.register0(promise);
                    }
                });
            } catch (Throwable var4) {
                AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
                this.closeForcibly();
                AbstractChannel.this.closeFuture.setClosed();
                this.safeSetFailure(promise, var4);
            }
        }

    }
}

@Override
public void execute(Runnable task) {
    // 省略非关键代码
    boolean inEventLoop = inEventLoop();
    // 添加任务
    addTask(task);
    if (!inEventLoop) {
        // 启动线程
        startThread();
        // 省略非关键代码
    }
    if (!addTaskWakesUp && wakesUpForTask(task)) {
        wakeup(inEventLoop);
    }
}

从上面这段代码中可以得知,这段代码是提交注册任务的代码,当符合条件的时候,就会执行startThread方法,代码如下:

io.netty.util.concurrent.SingleThreadEventExecutor#startThread

private void startThread() {
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            boolean success = false;
            try {
                // 真正执行启动线程方法
                doStartThread();
                success = true;
            } finally {
                if (!success) {
                    STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                }
            }
        }
    }
}

private void doStartThread() {
    assert thread == null;
    executor.execute(new Runnable() {
        @Override
        public void run() {
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
            }
            boolean success = false;
            updateLastExecutionTime();
            try {
                // 执行 NioEventLoop
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
                for (;;) {
                    // 省略非关键代码
                }
                if (success && gracefulShutdownStartTime == 0) {
                    // 省略非关键代码
                }
                try {
                    // 省略非关键代码
                } finally {
                    try {
                        //关闭selector
                        cleanup();
                    } finally {
                        // 省略非关键代码
                    }
                }
            }
        }
    });
}

SingleThreadEventExecutor#doStartThread方法的实现很简单,只是调用了一下doStartThread方法,关于doStartThread方法要复杂一些了,但是其核心代码也只是执行 NioEventLoop的run方法,代码如下:

io.netty.channel.nio.NioEventLoop#run

@Override
//死循环监听、处理事件
protected void run() {
    for (;;) {
        try {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.BUSY_WAIT:

                case SelectStrategy.SELECT:
                    // 执行selector.select
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                default:
                }
            } catch (IOException e) {
                rebuildSelector0();
                handleLoopException(e);
                continue;
            }
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    // 执行事件
                    processSelectedKeys();
                } finally {
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

private void closeAll() {
    selectAgain(); //这里的目标是为了去除canceled的key
    Set<SelectionKey> keys = selector.keys();
    Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
    for (SelectionKey k: keys) {
        Object a = k.attachment();
        if (a instanceof AbstractNioChannel) {
            channels.add((AbstractNioChannel) a);
        } else {
            k.cancel();
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            invokeChannelUnregistered(task, k, null);
        }
    }

    for (AbstractNioChannel ch: channels) {
        ch.unsafe().close(ch.unsafe().voidPromise());
    }
}

NioEventLoop的run方法是个死循环,期主要目的是轮训这来执行事件,这个时候便是进入到连接的核心代码了,至于这个方法也没什么特别多需要解释的,接下来就开始分析主线代码了。

执行selector.select

NioEventLoop通过selector.select/selectNow()/select(timeoutMillis)方法执行发现连接接收事件OP_ACCEPT时,然后对其进行处理,selector.select方法(对NIO的封装)代码如下:

private void select(boolean oldWakenUp) throws IOException {
    Selector selector = this.selector;
    try {
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        // 按scheduled的task时间来计算select timeout时间。
        long selectDeadLineNanos = currentTimeNanos + this.delayNanos(currentTimeNanos);

        while(true) {
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0L) { // 已经有定时task需要执行了,或者超过最长等待时间了
                if (selectCnt == 0) {
                    // 非阻塞,没有数据返回0
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }
            if (this.hasTasks() && this.wakenUp.compareAndSet(false, true)) {
                selector.selectNow();
                selectCnt = 1;
                break;
            }
    	// 下面select阻塞中,别人唤醒也可以可以的
            int selectedKeys = selector.select(timeoutMillis);
            ++selectCnt;
            if (selectedKeys != 0 || oldWakenUp || this.wakenUp.get() || this.hasTasks() || this.hasScheduledTasks()) {
                break;
            }
            if (Thread.interrupted()) {
                // 省略非关键代码
                selectCnt = 1;
                break;
            }
    	// 处理jdk空轮询bug和超时时的select方法的处理
            long time = System.nanoTime();
            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                selectCnt = 1;
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                selector = this.selectRebuildSelector(selectCnt);
                selectCnt = 1;
                break;
            }
            currentTimeNanos = time;
        }
        // 省略非关键代码
    } catch (CancelledKeyException var13) {
        // 省略非关键代码
    }

}

processSelectedKeys方法

从前面的run方法的代码中可以看出来,和之前一直提到的Netty Reactor模型图所展示的是不是一致的,当执行完processSelectedKeys()方法后,就会执行runAllTasks()。这里processSelectedKeys()方法的执行具体如下:

io.netty.channel.nio.NioEventLoop#processSelectedKeys

private void processSelectedKeys() {
    if (selectedKeys != null) {
        //不用JDK的selector.selectedKeys(), 性能更好(1%-2%),垃圾回收更少
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

NioEventLoop#processSelectedKeys方法的代码很简单,就是根据selectedKeys是否为null来决定执行哪个方法,这里会执行processSelectedKeysOptimized()方法,代码如下:

io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized

private void processSelectedKeysOptimized() {
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        selectedKeys.keys[i] = null;
        // 呼应于channel的register中的this: 例如:selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
        final Object a = k.attachment();
        // 
        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }
        if (needsToSelectAgain) {
            selectedKeys.reset(i + 1);
            selectAgain();
            i = -1;
        }
    }
}

从前面所展示的连接构建核心时序图中,可以得知当轮训到OP_ACCEPT事件的时候,会调用NioUnsafe.read()方法,而NioUnsafe则是通过processSelectedKeysOptimized()方法中的k.attachment()来进行获取的。 k.attachment()方法所返回的是AbstractNioChannel,AbstractNioChannel.unsafe()方法返回的则是AbstractNioChannel.NioUnsafe类型。那我们就来看看processSelectedKey()方法的具体实现,如下:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    // 获取 AbstractNioChannel.NioUnsafe
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    if (!k.isValid()) {
        final EventLoop eventLoop;
        try {
            eventLoop = ch.eventLoop();
        } catch (Throwable ignored) {
            return;
        }
        if (eventLoop != this || eventLoop == null) {
            return;
        }
        unsafe.close(unsafe.voidPromise());
        return;
    }
    try {
        int readyOps = k.readyOps();
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);
            unsafe.finishConnect();
        }
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            ch.unsafe().forceFlush();
        }
        //处理读请求(断开连接)或接入连接
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

执行NioMessageUnsafe#read 方法

从 processSelectedKey()方法中,看到当有OP_ACCEPT事件的时候,便会执行unsafe.read(),在unsafe.read()方法中,这时我们只需要关注doReadMessages(readBuf)即可,代码如下:

io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe#read

@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 {
                int localRead = doReadMessages(readBuf);
                // 省略非关键代码
            } 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();
        // 省略非关键代码
    } finally {
        // 省略非关键代码
    }
}

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    //接受新连接创建SocketChannel
    SocketChannel ch = SocketUtils.accept(javaChannel());
    try {
        if (ch != null) {
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
        // 省略非关键代码
    }

    return 0;
}

public NioSocketChannel(Channel parent, SocketChannel socket) {
    // 调用 NioSocketChannel 父类方法
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
    super(parent, ch, SelectionKey.OP_READ);
}

// io.netty.util.internal.SocketUtils#accept
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
    try {
        return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
            @Override
            public SocketChannel run() throws IOException {
                // 非阻塞模式下,没有连接请求时,返回null(对NIO的封装)
                return serverSocketChannel.accept();
            }
        });
    } catch (PrivilegedActionException e) {
        throw (IOException) e.getCause();
    }
}

doReadMessages方法中,会通过SocketUtils.accept(javaChannel())方法中的serverSocketChannel.accept()来创建SocketChannel对象,一但SocketChannel创建,就可以通过pipeline.fireChannelRead方法传播出去,在传播过程中,系统会调用处理程序流水线中的ServerBootstrapAcceptor对创建的SocketChannel来进行初始化,初始化方法为ServerBootstrapAcceptor#channelRead,那么就来看看这个方法在哪里调用的(AbstractNioMessageChannel.NioMessageUnsafe#read方法中),如下:

// io.netty.channel.DefaultChannelPipeline#fireChannelRead
public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(this.head, msg);
    return this;
}

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }

}

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
    if (this.invokeHandler()) {
        try {
            // channel read 执行逻辑
            ((ChannelInboundHandler)this.handler()).channelRead(this, msg);
        } catch (Throwable var3) {
            this.notifyHandlerException(var3);
        }
    } else {
        this.fireChannelRead(msg);
    }

}

上边的代码便是从.NioMessageUnsafe#read方法中开始调用 DefaultChannelPipeline#fireChannelRead 方法的一系列逻辑,代码很简单,这个就不赘述了,当调用到ServerBootstrapAcceptor#channelRead方法时,才是真正的核心逻辑,代码如下:

io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    // 获取 NioSocketChannel
    final Channel child = (Channel)msg;
    child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
    AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
    Entry[] var4 = this.childAttrs;
    int var5 = var4.length;

    for(int var6 = 0; var6 < var5; ++var6) {
        Entry<AttributeKey<?>, Object> e = var4[var6];
        child.attr((AttributeKey)e.getKey()).set(e.getValue());
    }

    try {
        // 注册客户端的读事件
        this.childGroup.register(child).addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
                }

            }
        });
    } catch (Throwable var8) {
        forceClose(child, var8);
    }

}

上面的初始化(注册)SocketChannel的过程和初始化ServerSocketChannel的过程是差不多的,最终都是从NioEventLoopGroup中(此时是Worker Group)中选择一个NioEventLoop。来注册“读”事件,但是此时的读事件是真正的OP_READ读取数据事件(也就是前面时序图中最后所提到的读事件),而不是OP_ACCEPT连接接收事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值