文章目录
线程模型图 - Reactor 模型
服务端编程模板
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
pipeline.addLast(new MyStringChannelInboundHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
channelFuture.channel().closeFuture().sync();
} catch (Throwable cause) {
log.error("", cause);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
核心组件类图
NioEventLoopGroup 和 NioEventLoop
通过类图要明确如下两条父子关系链, 子的身份既是子也是父, 调用子类覆盖的父类的某些方法时, 可以从最低的级别向上找
- NioEventLoopGroup > MultithreadEventLoopGroup > MultithreadEventExecutorGroup > AbstractEventExecutorGroup > EventExecutorGroup
- NioEventLoop > SingleThreadEventLoop > EventLoop > EventExecutor
NioServerSocketChannel 和 NioSocketChannel
- NioServerSocketChannel > AbstractNioMessageChannel > AbstractNioChannel > AbstractChannel
- NioSocketChannel > AbstractNioByteChannel > AbstractNioChannel > AbstractChannel
- AbstractNioMessageChannel里newUnsafe()新建的是NioMessageUnsafe实例, 即NioServerSocketChannel持有的unsafe是NioMessageUnsafe
- AbstractNioByteChannel里newUnsafe()新建的是NioByteUnsafe实例, 即NioSocketChannel持有的unsafe是NioByteUnsafe
创建 NioEventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
- 记住: 当前创建的对象是NioEventLoopGroup, 即使调用父类的构造函数, 也是在给NioEventLoopGroup实例完善字段, 包括调用父类的方法, 也要认为是调用自己的方法
- 入参没有指定nThreads, 则默认传0, 没有指定executor, 则默认传null, 先在NioEventLoopGroup中的各构造器之间跳转, 逐渐添加各种默认参数
- 跳到抽象父类MultithreadEventLoopGroup的构造器中, 在这里判断有没有传入nThreads, 没有传入的话用默认值(CPU逻辑核数乘以2)
- 跳到抽象父类MultithreadEventExecutorGroup的构造器中, 给 NioEventLoopGroup 的 children 和 chooser 属性赋值, children 的类型是 EventExecutor[], 其中的每一个项都是用子类 NioEventLoop 构建的
- 构造NioEventLoop时, 传入了executor等参数, 给 NioEventLoop 的 selector 赋值
- 跳到抽象父类SingleThreadEventLoop的构造器中
- 跳到抽象父类SingleThreadEventExecutor的构造器中, 给 NioEventLoop 的 executor和taskQueue 属性赋值
- 跳到抽象父类AbstractScheduledEventExecutor的构造器中
- 跳到抽象父类AbstractEventExecutor的构造器中, 给 NioEventLoop 的 parent 属性 赋值为 NioEventLoopGroup 的实例
NioEventLoopGroup 和 NioEventLoop 的关系
初始化 NioEventLoopGroup 的时候, 有两个比较重要的属性, 如下
// EventExecutorGroup 中维护的一组 EventExecutor
private final EventExecutor[] children;
// EventExecutorGroup.next() 方法依赖该选择器选择合适 EventExecutor 来执行任务
private final EventExecutorChooserFactory.EventExecutorChooser chooser;
其中 children 本质上是 NioEventLoop 数组实例, chooser 持有 children 的引用, 其 next() 方法可以从 children 中轮询出一个 NioEventLoop 实例来做其他操作. 从 NioEventLoopGroup 中获取一个 NioEventLoop 实例就是通过 chooser 的 next() 方法取到的. 这里有一个小优化点, chooser 在初始化时根据 nThreads 是否是2的指数, 初始化不同的实例, 都是轮询取出 child, 不过2的指数时使用了(x&(n-1))方式(n是2的指数次)来提升性能
创建并装配 ServerBootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
pipeline.addLast(new MyStringChannelInboundHandler());
}
});
- group(bossGroup, workerGroup), 给group和childGroup属性赋值
- channel(NioServerSocketChannel.class), 通过传入的channelClass, 给channelFactory属性赋值, 用于创建服务端channel, 这里用的是NIO的ServerSocketChannel的封装, NioServerSocketChannel.class
- option(ChannelOption.SO_BACKLOG, 1024), 给通道属性options设置相关的属性
- childHandler(new ChannelInitializer), 给childHandler属性赋值, 注意initChannel方法中添加的handler都是给SocketChannel用的而不是ServerSocketChannel
绑定端口
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
这篇文章解释了为什么 ServerSocketChannel 注册 accept 事件到 Selector 时为什么传入的感兴趣的事件不是 OP_ACCEPT(16) 而是 0, 这点和直接使用NIO相差很大. 其实这个地方注册的0并不是Selector真正感兴趣的事件, 而是在后面当 ServerSocketChannel 实例成功绑定端口后, 才将 register 的返回值的 SelectionKey 实例的感兴趣事件重新设置成 16, 这时 Selector 实例才真正开始处理 ACCEPT 事件
客户端连接服务端
服务端启动好后, 等待客户端连接, 当客户端连接后, 服务端 unsafe.read() 会收到ACCEPT(16)事件, 走accept流程
客户端向服务端写数据
客户端和服务端建立连接, SocketChannel建立好后, Selector等待客户端向SocketChannel中写数据, unsafe.read() 会收到READ(1)事件, 走read流程