前言:

    前一篇中我们介绍了NioServerSocketChannel,NioServerSocketChannel主要负责两件事:绑定(bind)到本地port,作为一个Endpoint;监听客户端连接事件,将获取到的连接注册到EventLoop中。

    那么数据的读写呢?NioServerSocketChannel不负责数据的读写,那么数据读写都交由NioSocketChannel来负责。

    本文主要讲解NioSocketChannel 处理连接(connect)、读(read)事件。

1.NioSocketChannel类结构图

netty SocketAddress 使用_ide

同样,直接开启Diagrams视角。

与前面NioServerSocketChannel有很多相同的接口和抽象类,AbstractChannel、AbstractNioChannel、SocketChannel等前一篇文章中,都已经有说明,本文不再赘述。

2.AbstractNioByteChannel解析

public abstract class AbstractNioByteChannel extends AbstractNioChannel {
 
    // 构造方法,OP_READ代表NioSocketChannel关注的事件,在注册EventLoop时会将监听事件也传入
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }
    
    // 同之前的分析一样,都包含一个NioByteUnsafe
    protected class NioByteUnsafe extends AbstractNioUnsafe {
     
        // 读取对端传入的数据
        @Override
        public final void read() {
            final ChannelConfig config = config();
            // 如果输入流已经关闭,则移除OP_READ监听
            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 {
                    // 从ByteBufAllocator分配器中获取ByteBuf
                    byteBuf = allocHandle.allocate(allocator);
                    // doReadBytes()方法负责从channel中读取数据,交由子类NioSocketChannel实现
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // 没有可读数据,则释放ByteBuf
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    // 获取到可读数据后,触发ChannelPipeline的读事件
                    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 {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

	...
    // 还有一系列的write方法,不是本文关注的重点呢,下一篇会介绍
    protected final int doWrite0(ChannelOutboundBuffer in) throws Exception {}
}

可以看到,AbstractNioByteChannel还是做了很多事情的,读写模板方法都在这里了。

读方法在NioByteUnsafe中,主要做了两件事情:从channel中读取数据到ByteBuf中;将读取到的ByteBuf交由ChannelPipeline处理,触发器read事件,进而触发其ChannelHandler.read方法

3.NioSocketChannel解析

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {

    // 用于创建SocketChannel的默认SelectorProvider
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
    private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
    }

	// 连接到远端
	protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            doBind0(localAddress);
        }

        boolean success = false;
        try {
            // 调用原生的socketChannel.connect(remoteAddress),还是NIO那一套
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            if (!connected) {
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

	// 从channel中读取数据
	// 接上面AbstractNioByteChannel.read方法,主要实现由以下来进行
	protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        // 直接调用ByteBuf.writeBytes方法,将SocketChannel的可读数据写入到ByteBuf中
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }

	/** 输入输出流状态判断
     *  主要还是调用原生的Socket来判断
     */
	@Override
    public boolean isActive() {
        SocketChannel ch = javaChannel();
        return ch.isOpen() && ch.isConnected();
    }

    @Override
    public boolean isOutputShutdown() {
        return javaChannel().socket().isOutputShutdown() || !isActive();
    }

    @Override
    public boolean isInputShutdown() {
        return javaChannel().socket().isInputShutdown() || !isActive();
    }

	// NioSocketChannelUnsafe基本没啥方法了,可以忽略
	private final class NioSocketChannelUnsafe extends NioByteUnsafe {
        // 将当前channel的关注事件从EventLoop中移除
        protected Executor prepareToClose() {}
    }
}

读事件本身比较简单,就是将数据从Channel中读取到ByteBuf中。

总结:

    有了前面文章的分析之后,再来分析NioSocketChannel就比较简单了。

    老规矩,通过两张时序图来说明下NioSocketChannel 连接(connect)、读取数据的整个过程

1.NioSocketChannel连接远端过程

    连接远端的起点肯定就是Bootstrap.connect(ip,port)方法了。时序图如下所示:

netty SocketAddress 使用_数据_02

 

    最终的实现连接的的方法还是调用NioSocketChannel.doConnect()

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
	protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            doBind0(localAddress);
        }

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

2.NioSocketChannel接收(read)消息

    客户端从哪里接收消息呢,有了之前分析NioServerSocketChannel的经验,我们就知道,是从EventLoop中。时序图如下:

netty SocketAddress 使用_java_03

 

    最关键的解析Channel中的输入流信息到ByteBuf中,该动作是由NioSocketChannel来执行的

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
    protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }
}

    后续会将ByteBuf交由一系列自定义的ChannelHandler来操作。