02 | Bootstrap源码分析(二)

一、Nio Client 刨析

先看下Netty4 客户端启动的demo代码:

// Configure the client.
EventLoopGroup group = new NioEventLoopGroup(1);
try {
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.group(group) //【线程模型】
     .channel(NioSocketChannel.class)  //【IO模型】
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)//连接超时时间
    .option(ChannelOption.SO_KEEPALIVE, true)//保活探测,若正常会回复ACK响应
    .option(ChannelOption.TCP_NODELAY, true) //开启Nagle算法【Nagle算法通过减少需要传输的数据包,来优化网络;提高实时性:true 提高网络利用率:false】
     .attr(AttributeKey.newInstance("clientName"), "X的netty客户端") // 用于给客户端的IO模型设置属性,即给NioSocketChannel设置属性
     .handler(new ChannelInitializer<SocketChannel>() {//【读写处理器】
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             //p.addLast(new LoggingHandler(LogLevel.INFO));
             p.addLast(new EchoClientHandler());
         }
     });
    // connect(bootstrap, HOST, PORT, 5);
    // Start the client.
    ChannelFuture f = bootstrap.connect(HOST, PORT).sync();
    // Wait until the connection is closed.
    f.channel().closeFuture().sync();
} finally {
    // Shut down the event loop to terminate all threads.
    group.shutdownGracefully();
}

01 NioEventLoopGroup创建

同Boss模式,唯一的区别是NioEventLoop的数量不一样,一般会是默认的CPU核数2
// Group线程初始化为1,不传参时默认为CPU核数
2

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

也需要经历以下步骤:
1)NioEventLoopGroup实例创建
2)NioEventLoop实例创建
3)NioEventLoop#Selector创建
4)NioEventLoop#Chooser创建

02 NioSocketChannel 创建

客户端的解析并连接,对应于服务端的注册并绑定。

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.isDone()) {
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        ...
    } else {
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                ...
            }
        });
        return promise;
    }
}

1)NioSocketChannel 实例创建

逻辑同Boss模式,这里会创建一个 NioSocketChannel 实例,其构造方法中newSocket创建了jdk的ServerSocketChannel,并预设置OP_READ变量。

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 实例创建
        channel = channelFactory.newChannel();
        // Bootstrap 初始化channel配置
        init(channel);
    } catch (Throwable t) {
        ...
    }
    // channel注册到Group线程池,由Group线程池来处理read事件
    ChannelFuture regFuture = config().group().register(channel);
    ...
}

实例创建的具体逻辑,最终使用jdk对象创建Channel

public NioSocketChannel() {
    this(DEFAULT_SELECTOR_PROVIDER);
}

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

private static SocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openSocketChannel();
    } catch (IOException e) {
        throw new ChannelException("Failed to open a socket.", e);
    }
}

参数绑定,这些参数作用于NioSocketChannel,因为客户端只有这么一个Channel。

void init(Channel channel) {
    ChannelPipeline p = channel.pipeline();
    // 添加启动时放进去的自定义handler
    p.addLast(config.handler());
    // 设置Options属性
    setChannelOptions(channel, newOptionsArray(), logger);
    setAttributes(channel, newAttributesArray());
}

2)SocketChannel#Selector关联

逻辑同Boss模式,这里的注册是指将新建出来的NioSocketChannel(JDK创建出来的Channel),与NioEventLoopGroup中NioEventLoop的unwrappedSelector相绑定。
NioEventLoop初始化时生成了Jdk的Selector,一个Selector在jdk中用来管理多个Channel,他是jdk开发出来对多个Channel操作与管理的一个功能。

final ChannelFuture initAndRegister() {
    ChannelFuture regFuture = config().group().register(channel);
}

调用AbstractChannel中内部类AbstractUnsafe的register方法,当前线程NioEventLoop作为参数,传递过去了

public ChannelFuture register(final ChannelPromise promise) {
    promise.channel().unsafe().register(this, promise);
}

绑定时关心的监听事件设置为0,表示对应Channel的FD相关事件不做监听,具体由register0中doRegister()方法实现,其调用AbstractNioChannel#doRegister方法,do相关方法就是真正干活的。

protected void doRegister() throws Exception {
  // channel注册到selector,但是监听事件为0。
  // 对client来说,监听事件应该是READ
  // 创建channel时,ACCEPT/READ事件以参数形式传递给父类AbstractNioChannel的readInterestOp成员变量,什么时候把ACCEPT/READ事件绑定到Selector上呢?最终会在AbstractNioChannel的doBeginRead方法中绑定到Selector上
  selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
  ...
}

3)NioSockerChannel#Handler关联

关联自定义handler到pipeline中,等待其nio-thread执行register0内的pipeline.invokeHandlerAddedIfNeeded()来触发。

void init(Channel channel) {
    ChannelPipeline p = channel.pipeline();
    // 添加启动时放进去的自定义handler
    p.addLast(config.handler());
    ...
}

03 NioSocketChannel 解析连接

1)NioSockerChannel#解析

NioSocketChannel注册流程结束的时候,会执行safeSetSuccess(promise)给promise对象设置返回值,触发Bootstrap给promise对象设置返回值,触发AbstractBootstrap.doResolveAndConnect中regFuture.addListener#doResolveAndConnect0方法。

private void register0(ChannelPromise promise) {
    try {
        // 触发ServerBootstrap.init中的ChannelInitializer.initChannel方法
        pipeline.invokeHandlerAddedIfNeeded();
        // Bootstrap给promise对象设置返回值,触发AbstractBootstrap.doResolveAndConnect中regFuture.addListener#doResolveAndConnect0方法,进行地址解析和连接
        safeSetSuccess(promise);
        ...       
}

doResolveAndConnect0进行地址解析和连接。

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    if (regFuture.isDone()) {
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
            }
        });
    }
}

2)NioSockerChannel#连接

在nio-thread中进行连接操作,最终通过pipeline调用AbstractNioChannel.AbstractNioUnsafe.connect方法,

private static void doConnect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
    final Channel channel = connectPromise.channel();
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            channel.connect(remoteAddress, connectPromise);
        }
    });
}

连接服务器,数据报文经过本机网络栈->网络->服务端网络栈->服务端的OP_Accept。
但此时只是往服务器发送连接请求了,还未真正完成连接。(tcp三次握手)
关注事件为OP_CONNECT客户端连接事件,操作系统会监听tcp第二次握手的ack报文。

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
    try {
        // 连接服务器
        boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
        if (!connected) {
            selectionKey().interestOps(SelectionKey.OP_CONNECT);
        }
    ...
}

3)NioSockerChannel#事件重置

NioEventLoop拿到ack报文后,先将关注事件设置为0,并标识当前Channel为已连接状态。

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch){
    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();
    }    
}

同时发送第三次握手报文,通过finishConnect通知Linux底层客户端已经完成连接建立了,在这之后如果获取SocketChannel是否打开、是否连接,都会返回true。

protected void doFinishConnect() throws Exception {
    if (!javaChannel().finishConnect()) {
        throw new Error();
    }
}

public boolean isActive() {
    SocketChannel ch = javaChannel();
    return ch.isOpen() && ch.isConnected();
}

4)NioSockerChannel#关注read

通过pipeline触发流水线上Handle的ChannelActive()方法,业务Handler可以埋入业务所需的逻辑,比如用户登录成功、连接建立成功等等。

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
    ...
    if (!wasActive && active) {
        pipeline().fireChannelActive();
    }
    ...
}
ChannelActive会继续触发readIfIsAutoRead相关方法,进行read事件的关注。
private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

protected void doBeginRead() throws Exception {
    // readInterestOp为accept、read事件等,java.nio.channels.SelectionKey         // boss:OP_ACCEPT        // workor:OP_READ
    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

二、总结

客户端的启动和服务端启动复用性比较高,特别是NioEventLoopGroup|NioEventLoop等逻辑思想都是一致的,唯一的区别就在于服务端是DoBind、客户端是doResolveAndConnect,客户端这里有一个三次握手的实现,可以结合TCP的三次握手协议对照看看。

如果有条件,后期还会补充对应jdk nio、linux epoll等处理逻辑,待续。

  • 32
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值