欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

网易云课堂-微专业Java

程序员文章站 2022-05-22 20:34:28
...

Netty学习笔记(一)

Netty线程模型

Java NIO虽然为开发者提供了功能强大的IO处理API,但是实际运用中去使用这样的API是比较繁琐的,而且如果抛开多线程技术,程序则无法达到高性能的指标。
Reactor模型将NIO和多线程结合,实现程序高性能。

  • 实现Reactor模型
 private ServerSocketChannel serverSocketChannel;
    // 1、创建多个线程 - accept处理reactor线程 (accept线程)
    private ReactorThread[] mainReactorThreads = new ReactorThread[1];
    // 2、创建多个线程 - io处理reactor线程  (I/O线程)
    private ReactorThread[] subReactorThreads = new ReactorThread[8];

在开源社区中,有很多对JDK NIO进行了封装的十分优秀的网络编程框架,其中应用较高的就有Netty。为了让NIO处理更好的利用多线程特性,Netty实现了Reactor线程模型。

  • Netty结构图

网易云课堂-微专业Java
可以看出Netty主要包括三个部分:左上方红色区域表示支持Socket等多种传输方式;右上方表示提供了多种协议编解码实现;下方表示核心设计,主要包括事件处理模型、API使用、ByteBuffer增强等。

  • Netty实现Reactor线程模型与上代码直接使用线程不同的是其是采用事件轮询机制。
        // 创建EventLoopGroup   accept线程组 NioEventLoop
        EventLoopGroup mainGroup = new NioEventLoopGroup(1);
        // 创建EventLoopGroup   I/O线程组
        EventLoopGroup subGroup2 = new NioEventLoopGroup(1);
        // 服务端启动引导工具类
            ServerBootstrap b = new ServerBootstrap();
            // 配置服务端处理的reactor线程组以及服务端的其他配置
            b.group(mainGroup, subGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline p = ch.pipeline();
                    p.addLast(new EchoServerHandler());
                }
            });

ServerBootstrap是Netty服务服务启动类。
group中的parent Group负责处理accept请求,child Group则负责处理具体的业务实现。
channel用于创建具体的通道实例,传入NIOServerChannel。
handler是用来处理服务端通道请求的。
childHandler是用来处理具体socket连接后的请求。
pipeline是责任链(后续学习)。

  • Reactor线程实现:多个请求同时达到服务端,EventLoop开始事件轮询,然后通过Dispatcher分发给各个时间处理器去处理具体的业务实现。
    网易云课堂-微专业Java
    网易云课堂-微专业Java
    上图中采用了两组不同的EventLoop处理不同通道的不同事件。Main EventLoopGroup主要轮询accept事件,然后将不同请求分发至SubEventLoopGroup中进行具体的I/O处理。

浅看new NioEventLoopGroup源码

  • 创建NioEventLoopGroup的时候,默认线程数是cpu数量*2,也可以显式指定线程数。
 static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

    /**
     * @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
     */
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
        //默认是CPU数*2
    }
  • 当调用excutor方法提交任务时,则会判断是否启动,未启动时则调用内置的excutor创建新线程来触发run方法执行

网易云课堂-微专业Java
网易云课堂-微专业Java

  • 创建多个NioEventLoop,形成NioEventLoopGroup
    网易云课堂-微专业Java

  • newChild是NioEventLoop的实现
    网易云课堂-微专业Java
    NioEventLoop具体完成哪些事情呢?

  • 将通道注册到EventLoop的selector上,进行事件轮轮询
    网易云课堂-微专业Java

  • 一步一步查看NioEventLoop父类,发现其最终继承Exector类。在SingleThreadEventExecute类中实现execute方法。
    网易云课堂-微专业Java

  • 执行run方法
    网易云课堂-微专业Java

  • 查看NioEventLoop中的run方法实现,其执行网络处理(selector)
    网易云课堂-微专业Java

  • 点击查看processSelectedKeys(),一步一步查看,查看到根据某个事件类型进行操作的代码
    网易云课堂-微专业Java
    – 注意ServerSocketChannel用的是NioMessageUnsafe,SocketChannel用的是NioByteUnsafe
    –EventLoop的启动
    EventLoop自身实现了Excutor接口,当调用excutor方法提交任务时,则会判断是否启动,未启动时则调用内置的excutor创建新线程来触发run方法执行。

服务端启动

            // 通过bind启动服务
            ChannelFuture f = b.bind(PORT).sync();
            // 阻塞主线程,知道网络服务被关闭
            f.channel().closeFuture().sync();

bind是创建通道,绑定端口。

浅看bind源码

网易云课堂-微专业Java

  • 初始化(selector在之前创建的eventLoop中)
    网易云课堂-微专业Java
  • 查看register方法

网易云课堂-微专业Java

  • 找到相应的register方法。(当子类找不到合适的方法时,就去父类中找)
    网易云课堂-微专业Java
  • 在AbstractChannel.java中unsafe()返回一个unsafe对象
    网易云课堂-微专业Java
  • 在构造函数中会将一个Unsafe对象赋值给unsafe变量
    网易云课堂-微专业Java
  • AbstractNioMessageChannel.java中newUnsafe()中返回一个NioMessageUnsafe
    网易云课堂-微专业Java
  • Unsafe().register()调用的是AbstractChannel中的register方法
    网易云课堂-微专业Java
  • register0()中调用doRegister()方法
    网易云课堂-微专业Java
  • doRegister方法将通道注册到selector中
    网易云课堂-微专业Java
  • 绑定端口
    网易云课堂-微专业Java
  • bind0方法
    网易云课堂-微专业Java
  • bind具体实现
    网易云课堂-微专业Java

至此,netty服务端服务就启动完成。