Netty源码分析之客户端

连接源码

首先从客户端开始,编写一个客户端代码

public class EchoClient {

    private final String host;      //主机号
    private final int port;         //端口

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient("127.0.0.1", 8080).start();
    }

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();      //创建Bootstrap对象
            b.group(group)
                    .channel(NioSocketChannel.class)      //设置channel为适用于NIO传输的channel
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            ChannelFuture f = b.connect().sync();       //连接到远程节点,阻塞等待知道连接完成
            f.channel().closeFuture().sync();       //阻塞,关闭channel
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

上面的代码展示了netty客户端初始化所需的所有内容
(1)为初始化客户端,创建一个Bootstrap实例
(2)为进行事件处理分配一个NioEventLoopGroup实例,其中事件处理包括创建新的连接以及处理入站和出站数据
(3)为服务器连接创建一个InetSocketAddress实例
(4)当连接建立时,一个EchoClientHandle实例会被安装到ChannelPipeline中
(5)完成一切设置后,调用Bootstrap.connect方法连接到远程节点

NioSocketChannel的初始化过程

在 Netty 中,Channel 是一个 Socket 的抽象,它为用户提供了关于 Socket 状态(是否是连接还是断开) 以及对 Socket 的读写等操作. 每当 Netty 建立了一个连接后,都会有一个对应的 Channel 实例.
NioSocketChannel 的类层次结构如下:
这里写图片描述
下面我们着重分析一下Channel的初始化过程:

除了TCP协议外,Netty还支持很多其他的连接协议(如UDP,Sctp),并且每种协议还有NIO和OIO(Old-IO)版本的区别,下面给出一些常用的Channel类型:

  • NioSocketChannel: 代表异步的客户端的TCP连接
  • NioServerSocketChannel:代表异步的服务端的TCP连接
  • NioDatagramChannel:异步的 UDP 连接
  • NioSctpChannel:异步的客户端 Sctp 连接.
  • NioSctpServerChannel:异步的 Sctp 服务器端连接.
  • OioSocketChannel:同步的客户端 TCP Socket 连接.
  • OioServerSocketChannel:同步的服务器端 TCP Socket 连接.
  • OioDatagramChannel:同步的 UDP 连接
  • OioSctpChannel:同步的 Sctp 服务器端连接
  • OioSctpServerChannel:同步的客户端 TCP Socket 连接

    如此多的Channel类型,那我们究竟如何选择我们的Channel呢?答案就是channel方法的调用

    在上面客户端连接代码的初始化Bootstrap中,会调用channel方法,传入NioSocketChannel,这个方法其实就是初始化了一个ReflectiveChannelFactory

/**
* The {@link Class} which is used to create {@link Channel} instances from.
* You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your
* {@link Channel} implementation has no no-args constructor.
*/
public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

ReflectiveChannelFactory实现了ChannelFactory接口,提供了一个newChannel来产生Channel的实例.而ChannelFactory一看就是工厂模式的体现,即一个产生Channel的工厂方法

@Override
public T newChannel() {
    try {
        return clazz.getConstructor().newInstance();        //通过反射来获取实例
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + clazz, t);
    }
}

根据上面客户端的连接代码,我们可以知道:

  • Bootstrap中ChannelFactory的具体实现是ReflectiveChannelFactory
  • Channel的实例化过程就是调用ChannelFactory#newChannel方法,而实例化Channel的具体类型又是在初始化Bootstrap 时传入的 channel() 方法的参数相关(客户端连接代码中是NioSocketChannel)

Channel的实例化时机
前面我们知道如何确定一个Channel的类型是通过ChannelFactory的newChannel来实例化,那newChannel这个方法究竟实在什么是调用的呢?
继续采用debug模式跟踪程序调用信息,我们发现调用链

Bootstrap.connect -> Bootstrap.doResolveAndConnect -> AbstractBootstrap.initAndRegister

在 AbstractBootstrap.initAndRegister中就调用了ChannelFactory.newChannel方法

/**
* Connect a {@link Channel} to the remote peer.
*/
public ChannelFuture connect() {
    validate();
    SocketAddress remoteAddress = this.remoteAddress;
    if (remoteAddress == null) {
        throw new IllegalStateException("remoteAddress not set");
    }
    return doResolveAndConnect(remoteAddress, config.localAddress());
}
/**
* @see #connect()
*/
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    //省略了无关代码
}
final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        channel = channelFactory.newChannel();        //实例化channel
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            channel.unsafe().closeForcibly();
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
    }

    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
    return regFuture;
}

在 newChannel 中, 通过类对象的 getConstructor().newInstance(); 来获取一个新 Channel 实例,,因而会调用NioSocketChannel 的默认构造器.
NioSocketChannel 默认构造器代码如下:

/**
* Create a new instance
*/
public NioSocketChannel() {
    this(DEFAULT_SELECTOR_PROVIDER);
}

/**
* Create a new instance using the given {@link SelectorProvider}.
*/
public NioSocketChannel(SelectorProvider provider) {
    this(newSocket(provider));
}

public NioSocketChannel(Channel parent, SocketChannel socket) {
    super(parent, socket);
    config = new NioSocketChannelConfig(this, socket.socket());
}

这里的newSocket会打开一个新的SocketChannel,接着会继续调用父类的构造器(AbstractNioByteChannel),
并传入参数parent为null,ch为刚才使用newSocket创建的Java NIO SocketChannel,因此生成的NioSocketChannel的parent channel为空

/**
* Create a new instance
*
* @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
* @param ch                the underlying {@link SelectableChannel} on which it operates
*/
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
    super(parent, ch, SelectionKey.OP_READ);        /NIO 中的读写事件
}

接着继续调用父类(AbstractNioChannel)的构造方法

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
         ch.configureBlocking(false);        //NIO 中设置为非阻塞
    } 
}

然后继续调用父类AbstractChannel的构造方法

protected AbstractChannel(Channel parent) {
    this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

到这里,一个完整的NioSocketChannel就初始化完成了

  • 调用NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打开一个新的 Java NIO SocketChannel
  • AbstactChannel中初始化AbstactChannel属性
    • parent属性置为null
    • pipeline是new DefaultChannelPipeline新创建的实例
  • AbstractNioChannel中的属性
    • SelectableChannel ch 被设置为 Java SocketChannel, 即 NioSocketChannel#newSocket 返回的 Java NIO SocketChannel.
    • readInterestOp 被设置为 SelectionKey.OP_READ
    • SelectableChannel ch 被配置为非阻塞的 ch.configureBlocking(false)
  • NioSocketChannel中的属性
    • SocketChannelConfig config = new NioSocketChannelConfig(this, socket.socket())

以上就是NioSocketChannel的初始化过程,因为Netty也是基于多路复用IO的,注意和Java NIO形成对比

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值