服务端
1.首先是实例化boss线程池和worker线程池
实例化的就是 NioEventLoopGroup;这里我假设boss线程池初始化为1个线程,worker线程初始化为 2*CPU个数的线程数。
说一下主要做了什么工作:
(1)指定了线程池中线程数、线程池的执行器是ThreadPerTaskExecutor;
(2)线程池中每个线程其实就是一个NioEventLoop,线程池指定了每个NioEventLoop都需要的参数:
SelectorProvider, SelectStrategyFactory, RejectedExecutionHandlers.reject()
并将这三个参数封装成 args 参数传入NioEventLoop 的构造器中。
(3)核心的实例化过程其实在NioEventLoopGroup的父类MultithreadEventExecutorGroup中,下面的就是介绍MultithreadEventExecutorGroup构造器中主要做了的工作:
(4)实例化线程池的线程执行器executor,默认是ThreadPerTaskExecutor;
(5)对于线程池中的线程数组 children 进行初始化,初始化时传入了 executor 和 args;初始化的函数核心就是new NioEventLoop对象,如下:
new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
从上面可知,线程池中所有的线程公用executor, SelectorProvider, SelectStrategyFactory以及RejectedExecutionHandler。
这里需要说明的一点是,线程池中所有的线程都公用的SelectorProvider, 而SelectorProvider是通过NioEventLoopGroup里面的构造器里面的
SelectorProvider.provider(); 方式获取的, 而这个方法返回的是一个单例的SelectorProvider, 所以所有的NioEventLoop公用同一个单例SelectorProvider。
这个地方从一定的侧面能够说明一些问题,也就是说,对于boss线程池,如果在单机情况下,即时配置了多个线程,但是多个线程公用的是一个SelectorProvider。
(6)下面说明NioEventLoop 线程初始化时候做的核心工作:
- 通过 SelectorProvider 给线程构造一个selector;
- 给每个 NioEventLoop线程构造一个 任务队列taskQueue,默认是一个无界队列。
- 指定当前NioEventLoop 所属于的 NioEventLoopGroup
注意,自此还没有启动服务端的线程模型,仅仅只是初始化了线程池。
2.配置服务端ServerBootstrap
实例化ServerBootstrap时候并没有做什么工作,主要是通过ServerBootstrap这个引导类来配置netty的服务端。下面以netty的官方示例 EchoServer为例,来解释一下:
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new EchoServerHandler());
}
});
//调用sync()方法会一直阻塞等待channel的停止
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
(1)调用group(bossGroup, workerGroup)设置服务端的线程模型:
这里将boss线程池是设置给了 AbstractBootstrap 类里面的group属性;
将worker线程池设置给了 ServerBootstrap 类里面的childGroup属性,表示子线程池,也就是IO和业务处理线程池。
(2)调用channel(NioServerSocketChannel.class)方法:
这个方法里面主要实例化了 AbstractBootstrap里面的类属性channelFactory,也就是创建Channel的工厂,默认是ReflectiveChannelFactory。
(3)调用了option(ChannelOption.SO_BACKLOG, 100),设置AbstractBootstrap类里面的 option 属性。
(4)调用handler(new LoggingHandler(LogLevel.INFO)) 设置AbstractBootstrap类里面的 handler属性
(5)调用childHandler(childHandler) 设置了ServerBootstrap 类里面的 childrenHandler 属性。
(6)服务端调用b.bind(PORT) 来绑定本地的端口,这一步就是最重要的步骤了,下面仔细说说这个函数主要做了哪些工作:
(1)调用 AbstractBootstrap.bind(int inetPort) –>
(2)调用 doBind(localAddress);这个类首先初始化服务端的通道,然后做更底层的绑定操作。下面解释一下doBind()函数做了什么:
(3)调用initAndRegister()函数,主要是功能如下:实例化服务端的NioServerSocketChannel 并对Channel进行初始化; 将channel 注册到boss线程池的 NIOEventLoopGroup中。
NioServerSocketChannel实例化过程
创建NioServerSocketChannel是通过channelFactory.newChannel() 实现的,通过调用 NioServerSocketChannel 的无参构造器。 注意这里就与 Java的NIO联系起来了, 在 NioServerSocketChannel 构造器中,通过NIO的SelectorProvider创建了java.nio.channels.ServerSocketChannel;
这里先给出NioServerSocketChannel 的主要继承关系:
NioServerSocketChannel –》
AbstractNioMessageChannel –》
AbstractNioChannel –》
AbstractChannel
在NioServerSocketChannel 类中主要是利用 SelectorProvider 来创建了创建了一个 ServerSocketChannel 并作为参数调用父类构造器:
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
上面也设置了 当前通道感兴趣的事件是OP_ACCEPT 接收连接的事件;
在AbstractNioMessageChannel类构造其中,直接调用父类构造器
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
在 AbstractNioChannel 类构造器中,主要是将ServerSocketChannel赋值给AbstractNioChannel属性,设置ServerSocketChannel为非阻塞;
在AbstractChannel类构造器中就做了相当重要的工作,这里必须贴出源码:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
其中也就是一个非常重要的属性pipeline,对于pipeline的初始化其实没有太对内容,就是构建了一个由AbstractChannelHandlerContext 为结点的双向链表,来容纳ChannelHandler,这里先说一句,AbstractChannelHandlerContext与 channelHandler也是一一对应的关系。所以pipeline可以理解成Channelhandler的容器。
pipeline = newChannelPipeline();
这里也就说明了,每一个Channel与一个 Channelpipeline 是一一对应,也就是一一绑定的。
NioServerSocketChannel初始化
初始化过程其实也就是调用 ServerBootstrap.inti(channel),主要做了以下工作:
- 绑定TCP可选参数;
- 绑定附加参数;
- 对于worker线程池: 为Channel绑定的pipeline容器添加 各种handler
值得注意的是,在init()里面第一次调用了 NioEventLoop的execute(Runnable())方法,在研究NioEventLoop的源码的时候我们就知道了,第一次调用NioEventLoop的execute()方法的时候,会启动NioEventLoop线程。
将NioServerSocketChannel绑定到boss线程池中
ChannelFuture regFuture = config().group().register(channel);
其实就是调用SingleThreadEventLoop.register(final ChannelPromise promise)
调用:
promise.channel().unsafe().register(this, promise);
这里实际上就是调用 AbstractChannel.AbstractUnsafe.register()方法了,将这个channel注册到 一个 NioEventLoop 中,也可以理解成一个 Channel与一个NioEventLoop的绑定。
底层就是继续将调用 pipeline的fireChannelRegistered();方法, 这里也就是进入了pipeline 容器里面了,前面我们就说过pipeline里面有一个ChannelHandler双链表,这时就是从head指针开始在handler中执行了。
到这里doBind()函数里面的 initAndRegister()函数已经分析完毕了。
接下来就是等待注册完毕,当注册完毕之后,就会调用:
doBind0(regFuture, channel, localAddress, promise);
在doBind0()中将由channel所属于的NIoEventLoop来执行一个task,不过doBind0()将会在channelRegistered() 方法之前被调用,主要是为用户handler提供在其channelRegistered()实现中设置管道的机会。在doBind0()中主要是为channel绑定一个Channel关闭失败监听器。
在bind()函数的最后就是同步等待关闭通道了。