客户端 BootStrap

Bootstrap 是 Netty 提供的一个便利的工厂类, 我们可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化。

Channel 简介

Netty 中,Channel 是一个 Socket 的抽象。每当 Netty 建立了一个连接后, 都创建一个对应的 Channel 实例。

不同的 Channel 类型

除了 TCP 协议以外,Netty 还支持很多其他的连接协议, 并且每种协议还有 NIO(非阻塞 IO)和 OIO(Old-IO, 即传统的 阻塞 IO)版本的区别。不同协议不同的阻塞类型的连接都有不同的 Channel 类型与之对应。

类名Value
NioSocketChannel异步非阻塞的客户端 TCP Socket 连接。
NioServerSocketChannel异步非阻塞的服务器端 TCP Socket 连接。
OioSocketChannel同步阻塞的客户端 TCP Socket 连接。
OioServerSocketChannel同步阻塞的服务器端 TCP Socket 连接。

Channel 的生命周期

在这里插入图片描述

客户端代码实现

大致可以分为五步:

  1. 创建EventLoopGroup
  2. 创建Bootstrap并设置相关参数:
    1)channel类型,客户端:NioSocketChannel,服务端:NioServerSocketChannel
    2)设置option参数,
    3)设置handler处理器
  3. connect服务器ip,端口 并启动
public class ChatClient {
    public ChatClient connect(int port, String host, final String nickName) {
        EventLoopGroup group = new NioEventLoopGroup();	// 指定一个 NIO 的 EventLoopGroup
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)	// 初始化ReflectiveChannelFactory对象,NioSocketChannel作为其属性
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {	// 设置处理数据的 Handler
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ...
                }
            });
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync(); //发起同步连接操作
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully(); //关闭,释放线程资源
        }
        return this;
    }

    public static void main(String[] args) {
        new ChatClient().connect(8080, "localhost", "Tom");
    }
}

EventLoopGroup 的初始化

Netty幕后使用EventLoop,用EventLoop分配给每一个Channel来处理所有的事件,包括如下:

1)注册所有自己关注的事件

2)分发或者调度对应的事件给那些注册该事件的ChannelHandlers

3)指定下一个处理流程

EventLoop本身就是仅有一个线程去驱动的,但是它可以管控一个Channel的所有I/O事件,并且EventLoop整个生命周期不会变更,仅此一个线程实例。

NioEventLoopGroup 有几个重载的构造器,不过最终都是调用的父类 MultithreadEventLoopGroup 的构造器。

在这里插入图片描述

MultithreadEventLoopGroup.class

如果入参线程数 nThreads=0,那么 Netty 会设置默认的线程数 DEFAULT_EVENT_LOOP_THREADS

DEFAULT_EVENT_LOOP_THREADS:如果系统属性中没有设置io.netty.eventLoopThreads,则返回(处理器核心数 * 2)。

	private static final int DEFAULT_EVENT_LOOP_THREADS = 
	Math.max(1, SystemPropertyUtil.getInt(
	"io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
	
	……
	
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

MultithreadEventExecutorGroup.class

  1. new EventExecutor[nThreads],创建一个大小为 nThreads 的 SingleThreadEventExecutor 数组。
  2. this.newChild((Executor)executor, args),初始化 children 数组。
  3. chooserFactory.newChooser(this.children),根据 nThreads 的大小,创建不同的 Chooser,即如果 nThreads 是 2 的幂,则使用 PowerOfTwoEventExecutorChooser,反之使用 GenericEventExecutorChooser。children 数组是Chooser的属性。
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
     		……
        if (nThreads <= 0) {
           	……
        } else {
            ……
            this.children = new EventExecutor[nThreads];	// 创建一个大小为 nThreads 的 SingleThreadEventExecutor 数组

            int j;
            for(int i = 0; i < nThreads; ++i) {
                boolean success = false;
                boolean var18 = false;

                try {
                    var18 = true;
                    this.children[i] = this.newChild((Executor)executor, args);	// 返回一个 EventExecutor 实例,初始化 children 数组,
                    success = true;
                    var18 = false;
                } catch (Exception var19) {
                    ……
                } finally {
                   ……
                }
			……
            }

            this.chooser = chooserFactory.newChooser(this.children); // 从 children 数组中选出一个合适的 EventExecutor 实例
            ……
        }
    }

EventLoopGroup 的初始化过程总结:

  1. 设置nThreads 大小,如果没有指定,则取值:处理器核心数 * 2
  2. EventLoopGroup的父类 MultithreadEventExecutorGroup 内部维护了一个大小是 nThreadsEventExecutor[] children,构成了一个线程池。
  3. newChild()是一个抽象方法,由子类NioEventLoopGroup初始化 children 数组,每个元素都是NioEventLoop
  4. 根据 nThreads 的大小,创建不同的 ChooserPowerOfTwoEventExecutorChooserGenericEventExecutorChooser)。

Bootstrap 的初始化

bootstrap.channel(NioSocketChannel.class)

初始化ReflectiveChannelFactory.classNioSocketChannel.class 作为其属性

AbstractBootstrap.class:

       public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        } else {
            return this.channelFactory((
            io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
        }
    }

ReflectiveChannelFactory.class

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        } else {
            this.clazz = clazz;
        }
    }

    public T newChannel() {
        try {
            return (Channel)this.clazz.newInstance();
        } catch (Throwable var2) {
            throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
        }
    }

    public String toString() {
        return StringUtil.simpleClassName(this.clazz) + ".class";
    }
}

ReflectiveChannelFactory.newChannel()何时调用?

newChannel()用来创建NioSocketChannel 的实例,所以NioSocketChannel 的实例何时创建?

bootstrap.connect(SERVER_HOST, port)的initAndRegister方法中,调用了newChannel方法。

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
           ……
        }
	……
        return regFuture;
    }

在这里插入图片描述

客户端 Channel 的初始化总结

  1. 创建ReflectiveChannelFactory实例,用于封装NioSocketChannel.class,并提供newChannel()方法,用于反射出NioSocketChannel实例。

bootstrap.option

设置通道的选项参数, 对于服务端而言就是ServerSocketChannel, 客户端而言就是SocketChannel;

bootstrap.option(ChannelOption.TCP_NODELAY, true)

bootstrap.handler(handler处理器)

handler方法主要就是添加一个ChannelHandler , ChannelInitializer实现了ChannelHandler接口,重写initChannel()。

先将ChannelInitializer加入到Pipeline。 通过ChannelInitializer的回调方法handlerAdded(),调用 initChannel()方法,最终将各种 handler 添加到ChannelPipeline 中,实现handler的链式调用

bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline()
        .addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
        .addLast("frameEncoder", new LengthFieldPrepender(4))
        .addLast("encoder", new ObjectEncoder())
        .addLast("decoder", 
        new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)))
    }
})

在这里插入图片描述

ChannelHandlerContext

每个 ChannelHandler 被添加到 ChannelPipeline 后,都会创建一个 ChannelHandlerContext 并与之创建的 ChannelHandler 关联绑定。

ChannelHandlerContext、ChannelHandler、ChannelPipeline 的关系:

在这里插入图片描述

Bootstrap 的连接(bootstrap.connect)

AbstractBootstrap.class #doResolveAndConnect:

    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        ChannelFuture regFuture = this.initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.isDone()) {
            return !regFuture.isSuccess() ? regFuture : this.doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } else {
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        Bootstrap.this.doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }

                }
            });
            return promise;
        }
    }

initAndRegister channel

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            ……
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        ……
        return regFuture;
    }

newChannel()

获取SocketChannel 的实例,详见 《NioSocketChannel 的实例》

init(channel)

对Channle进行初始化配置。

  1. 创建pipeline
  2. 添加Handler到链表末端
  3. 获取options参数
  4. 将options参数赋值到前面初始化的NioSocketChannelConfig对象里
	void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();	// 创建pipeline
        p.addLast(new ChannelHandler[]{this.config.handler()});	// 添加Handler到链表末端
        Map<ChannelOption<?>, Object> options = this.options0(); // 获取options参数
        synchronized(options) {
            Iterator i$ = options.entrySet().iterator();

            while(true) {
                if (!i$.hasNext()) {
                    break;
                }

                Entry e = (Entry)i$.next();
						
                try {	// 将options参数赋值到前面初始化的NioSocketChannelConfig对象里
                    if (!channel.config().setOption((ChannelOption)e.getKey(), e.getValue())) {
                        logger.warn("Unknown channel option: " + e);
                    }
                } catch (Throwable var10) {
                    logger.warn("Failed to set a channel option: " + channel, var10);
                }
            }
        }

        Map<AttributeKey<?>, Object> attrs = this.attrs0();
        synchronized(attrs) {
            Iterator i$ = attrs.entrySet().iterator();

            while(i$.hasNext()) {
                Entry<AttributeKey<?>, Object> e = (Entry)i$.next();
                channel.attr((AttributeKey)e.getKey()).set(e.getValue());
            }

        }
    }

register(channel)

  1. Channel 与对应的 EventLoop 关联。在 Netty 中,每个 Channel 都会关联一个特定的 EventLoop,并且这个 Channel 中的所有 IO 操作都是在这个 EventLoop 中执行的;
  2. 关联好 Channel 和 EventLoop 后,通过unsafe 的 register方法,最终通过底层 Java NIO(AbstractNioChannel.class) 的 SocketChannel 对象的 register()方法,将底层 Java NIO 的 SocketChannel 注册到指定的 selector 中。

AbstractNioChannel.class

    protected void doRegister() throws Exception {
        boolean selected = false;

        while(true) {
            try {	// 注册
                this.selectionKey = this.javaChannel().register(this.eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException var3) {
                if (selected) {
                    throw var3;
                }

                this.eventLoop().selectNow();
                selected = true;
            }
        }
    }

doResolveAndConnect0

最终通过底层 Java NIO 的 SocketChannel.classdoConnect方法完成 NIO 底层的 Socket 的连接。

DefaultChannelPipeline.HeadContext #connect

public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
	this.unsafe.connect(remoteAddress, localAddress, promise);
}

NioSocketChannel.class

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
       ……
        try {
            boolean connected = this.javaChannel().connect(remoteAddress);
           ……
        } finally {
            ……
        }

        return var5;
    }

客户端 BootStrap 发起连接请求的流程,时序图:
在这里插入图片描述

NioSocketChannel 的实例

newChannel()方法中, clazz.newInstance()利用反射机制创建 Channel 实例,相当于调用 NioSocketChannel 的默认构造器。

NioSocketChannel.class

    public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }

    public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));	// 打开一个新的 Java NIO 的 SocketChannel
    }
    
    public NioSocketChannel(java.nio.channels.SocketChannel socket) {
        this((Channel)null, socket);
    }

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

SelectorProvider.class

打开一个新的 Java NIO 的 SocketChannel。

    private static java.nio.channels.SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();	// 打开一个新的 Java NIO 的 SocketChannel
        } catch (IOException var2) {
            throw new ChannelException("Failed to open a socket.", var2);
        }
    }

AbstractNioByteChannel.class

设置:SelectionKey.OP_READ

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

AbstractChannel.class

初始化 unsafe pipeline

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

在这里插入图片描述

Unsafe 的初始化

unsafe 特别关键,它封装了对 Java 底层 Socket 的操作,是Netty 和 Java 底层沟通的重要桥梁。

AbstractChannel.class

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

	……
	
	protected abstract AbstractChannel.AbstractUnsafe newUnsafe();

NioSocketChannel.class

返回一个 NioSocketChannelUnsafe 实例。

    protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannel.NioSocketChannelUnsafe(); // 返回一个 NioSocketChannelUnsafe 实例
    }

Pipeline 的初始化

在 Netty 中,一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表。这个链表的头是 HeadContext,链表的尾是 TailContext,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler

在这里插入图片描述

AbstractChannel.class

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

	……
	
	protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }


DefaultChannelPipeline.class

DefaultChannelPipeline的构造器需要传入一个 channel,其实就是 NioSocketChannel

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
        this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
        this.voidPromise = new VoidChannelPromise(channel, true);
        this.tail = new DefaultChannelPipeline.TailContext(this);
        this.head = new DefaultChannelPipeline.HeadContext(this);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

Pipeline 的事件传播机制(Inbound 事件和 Outbound 事件)

Netty 中的传播事件可以分为两种:Inbound 事件和 Outbound 事件。

Outbound 事件总结:
  1. Outbound 事件是请求事件(由 connect()发起一个请求,并最终由 unsafe 处理这个请求)。
  2. Outbound 事件的发起者是 Channel。
  3. Outbound 事件的处理者是 unsafe。
  4. Outbound 事件在 Pipeline 中的传输方向是 tail -> head。
  5. 在 ChannelHandler 中处理事件时,如果这个 Handler 不是最后一个 Handler,则需要调用 ctx 的方法(如: ctx.connect()方法)将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。
  6. Outbound 事件流:Context.OUT_EVT() -> Connect.findContextOutbound() -> nextContext.invokeOUT_EVT() -> nextHandler.OUT_EVT() -> nextContext.OUT_EVT()
Inbound 事件总结:
  1. Inbound 事件是通知事件,当某件事情已经就绪后,通知上层。
  2. Inbound 事件发起者是 unsafe。
  3. Inbound 事件的处理者是 Channe,如果用户没有实现自定义的处理方法,那么 Inbound 事件默认的处理者是 TailContext,并且其处理方法是空实现。
  4. Inbound 事件在 Pipeline 中传输方向是 head -> tail。
  5. 在 ChannelHandler 中处理事件时,如果这个 Handler 不是最后一个 Handler,则需要调用 ctx.fireIN_EVT()事 件(如:ctx.fireChannelActive()方法)将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。
  6. Outbound 事件流:Context.fireIN_EVT() -> Connect.findContextInbound() -> nextContext.invokeIN_EVT() -> nextHandler.IN_EVT() -> nextContext.fireIN_EVT().

一个事件可以被一系列的事件处理器处理:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值