【Netty】处理新连接(二):创建客户端channel(NioSocketChannel)

【Netty】处理新连接(二):创建客户端channel(NioSocketChannel)

Scroll Down

回顾

上一节中,我简单的记录了Netty获取一个新连接的过程,而在这个过程中,其实还有非常重要的一步没有展开,那就是创建客户端channel,即创建NioSocketChannel。

如果NioSocketChannel非常陌生,可以去看看我前面写的小总结,可以有一个大概的了解。

Netty Version:4.1.6


大致流程

  • new NioSocketChannel(parent, socket) - 入口

    • AbstractNioByteChannel(parent, ch) - super

      • AbstractNioByteChannel(parent, sh , readInterestOp)
        • AbstractChannel构造 - 创建id,pipeline,unsage等
        • ch.configureBlocking(false) - 配置nio
    • new NioSocketChannelConfig(channel, javaSocket) - 配置类

      • setTcpNoDelay(true) - 关闭Nagle算法

跟进源码

首先,先找到这次跟进源码的入口,即:io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages,在上一节文末处:

    @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = javaChannel().accept();

        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;
    }

跟进上面的的new NioSocketChannel(this, ch),此处【坐标1】:
io.netty.channel.socket.nio.NioSocketChannel#NioSocketChannel(io.netty.channel.Channel, java.nio.channels.SocketChannel)

    public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }
  • 这段代码还真是相当眼熟了,在创建服务端channel那一节也遇到过,只是这里换成客户端channel,逻辑大致相似。

这里先进入super构造方法:
io.netty.channel.nio.AbstractNioByteChannel#AbstractNioByteChannel

    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }
  • 这里和创建服务端channel不同的是,readInterestOp参数变为了OP_READ(值为1),也就是说客户端channel后面会关注一个读事件。

继续进入super:
io.netty.channel.nio.AbstractNioChannel#AbstractNioChannel

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            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);
        }
    }
  • 创建服务端channel不同的是,这里的parent不再为null,而是服务端channel(channel)。
    • image.png
  • 其它逻辑大致相同,核心同样是ch.configureBlocking(false)设置nio模型。

继续进入super:
io.netty.channel.AbstractChannel#AbstractChannel(io.netty.channel.Channel)

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }
  • 这段除了parent存储的是NioServerSocketChannel和newUnsage()返回的是NioSocketChannelUnsafe外,其余和创建服务端channel都是相同逻辑。

如果对客户端unsafe(NioSocketChannelUnsafe)和服务端unsafe(NioMessageUnsafe)感到非常陌生,可以参考之前的小总结


好了,上面追踪了一下构造函数,对比创建服务端channel,发现也就是替换了一些实现。下面将视角重新拉回上面的【坐标1】,来对比下config有什么不同。


跟进new NioSocketChannelConfig构造方法:
io.netty.channel.socket.nio.NioSocketChannel.NioSocketChannelConfig#NioSocketChannelConfig

        private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) {
            super(channel, javaSocket);
        }

继续跟进super构造方法:
io.netty.channel.socket.DefaultSocketChannelConfig#DefaultSocketChannelConfig

    public DefaultSocketChannelConfig(SocketChannel channel, Socket javaSocket) {
        super(channel);
        if (javaSocket == null) {
            throw new NullPointerException("javaSocket");
        }
        this.javaSocket = javaSocket;

        // Enable TCP_NODELAY by default if possible.
        if (PlatformDependent.canEnableTcpNoDelayByDefault()) {
            try {
                setTcpNoDelay(true);
            } catch (Exception e) {
                // Ignore.
            }
        }
    }
  • 这段代码其实和服务端config的代码是不一样的,只是之前没贴,下面就贴出来对比一下。

服务端channel的config:
io.netty.channel.socket.DefaultServerSocketChannelConfig#DefaultServerSocketChannelConfig

    public DefaultServerSocketChannelConfig(ServerSocketChannel channel, ServerSocket javaSocket) {
        super(channel);
        if (javaSocket == null) {
            throw new NullPointerException("javaSocket");
        }
        this.javaSocket = javaSocket;
    }
  • 按照我当初Server.java的配置,其实就是初始化一些tcp的属性。

对比服务端的config和客户端的config,很容易就发现客户端多了下面这一段:

setTcpNoDelay(true);
  • 这段代码的作用其实就是关闭Nagle算法,目的是让小数据包更快的发送出去。

当完成了NioSocketChannelConfig的构造后,整个NioSocketChannel就算创建完成了,后续跟创建服务端channel一样,也会初始化(注册、绑定等),关于初始化这里就不展开了,留到下一篇博客。


小结

  • NioSocketChannel(客户端channel)是在NioServerSocketChannel(服务端channel)中构建的。
  • NioSocketChannel的parent属性保存了NioServerSocketChannel的实例,并且其中的unsafe实现也与NioServerSocketChannel创建的不同。
  • NioServerSocketChannel创建后关注的是OP_ACCEPT事件,而本节的NioSocketChannel关注的则是OP_READ事件。
  • NioSocketChannel在构建NioSocketChannelConfig实例时,关闭了Nagle算法,目的是让小数据包更快发送,降低响应延迟。