01--Netty--001、Netty初认识

第一节,Netty初认识

Netty的优势

  • 网络应用框架的关注点:
    • I/O模型,线程模型和事件处理模型
    • 易用性API接口
    • 对数据协议、序列化的支持
IO模型
  • IO请求分为两个阶段,调用阶段和执行阶段
    • IO调用阶段:用户进程向内核发起系统调用,即请求数据发出的过程
    • IO执行阶段:内核等待IO请求处理完成(包括网络IO和磁盘IO)返回。又可分为两个阶段
      • 等待数据就绪,写入内核缓存区,即你需要的数据,已经写入的内存中,但是无法给用户来使用
      • 将内核缓存区数据拷贝到用户态缓存区,此过程正是将数据拷贝到用户可以使用的内存中,让用户使用
  • 五种IO模型
    • 同步阻塞IO
    • 同步非阻塞IO
    • IO多路复用
    • 信号驱动IO:内核通过信号通知用户态可以获取结果,用户态发起请求,获取结果
    • 异步IO:数据处理完成,直接从内核缓冲区拷贝到用户缓冲区,无需由用户缓存区,再发起一次获取数据的请求
  • Netty的IO模型
    • 基于非阻塞IO模型,底层依赖的是JDK NIO框架的多路复用器Selector,一个多路复用器Selector可以同时轮询多个Channel,采用epoll模式后,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端
    • 当数据准备就绪时,需要一个事件分发器,有两种设计模式
      • Reactor:采用同步IO,适合处理耗时短的场景,对于耗时长的IO操作容易造成阻塞
      • Proactor:采用异步IO,实现逻辑复杂
  • Netty相比于JDK NIO的优势
    • 易用性,JDK NIO很多复杂的概念,Netty进行了封装
    • 稳定性
    • 可扩展性
      • 可定制化线程模型(源码重点)
      • 可扩展的事件驱动模型,将框架层和业务层的关注点分离(额。。。,后面关注源码看看)
    • 更低的资源消耗
      • 对象池复用技术
      • 零拷贝技术

第二节、Netty开始

Netty整体架构

  • 三部分:Protocol负责服务之间的协议定义,相当于语言,而Transport用于将数据进行传递,而核心逻辑有Core服务提供支持
    • Core核心层
      • 提供了底层网络通信的通用抽象和实现,包括可扩展的事件模型、通用的通信API、支持零拷贝的ByteBuf等
    • Protocol Support协议支持层
      • 基本上支持了主流协议的编码实现
    • Transport Service传输服务层
      • 提供了网络传输能力的定义和实现方法。支持Socket HTTP隧道
  • 那么Netty如何分层呢???

Netty的逻辑处理架构

  • 为典型的网络分层架构设计
    • 网络通信层
    • 事件调度层
    • 服务编排层
网络通信层
  • 核心组件
    • BootStrap
    • ServerBootStrap
    • Channel
BootStrap & ServerBootStrap
  • BootStrap是引导的意思,主要负责整个Netty程序的启动、初始化、服务器连接等过程,相当于一条主线,串联了Netty的其他核心组件
  • BootStrap用于连接远端服务器,只绑定一个EventLoopGroup
  • ServerBootStrap用于服务端启动绑定本地端口,会绑定两个EventLoopGroup,通常称为Boss和Worker
    • Boss用来不断接受新的连接
    • Worker用来进行做事情
Channel
  • 通道,是指网络通信的载体,提供了基本的API用于网络I/O操作,提供了与底层Socket交互的能力。
  • 存在多个状态:连接建立,连接注册,数据读写,连接销毁,所以,对于连接而已就是通道,就是Channel相关的状态,随着状态的变化,Channel处于不同的生命周期中,每一个状态都会有一个绑定的回调事件
  • 那么Channel在不同生命周期时,具体执行的业务逻辑,或者谁来执行Channel所绑定的事件呢???答案就是事件调度层来执行
事件调度层
  • 事件调度层就是执行Channel中绑定的事件,职责是通过Reactor线程模型对各类事件进行聚合处理,通过Selector主循环线程集成多种事件(I/O事件、信号事件、定时事件等),实际的业务处理逻辑是交由服务编排层相关的Handler完成。
  • 核心组件:EventLoopGroup、EventLoop
EventLoopGroup & EventLoop
  • EventLoopGroup本质是一个线程池,负责接收I/O请求,并分配线程执行处理请求
  • EventLoopGroup、EventLoop、Channel的关系
    • 一个EventLoopGroup往往包含一个或者多个EventLoop.EventLoop用于处理Channel生命周期内的所有I/O事件,如accept、connect等I/O事件
    • EventLoop同一时间会与一个线程绑定,每个EventLoop负责处理多个Channel
    • 当一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该Channel的整个生命周期中都是由这个绑定的 EventLoop 来服务的。
事件编排层
  • 职责:负责组装各类服务,是Netty的核心处理链,用以实现网络事件的动态编排和有序传播(感觉有点责任链的样子呀,后面看看具体的源码逻辑)
  • 核心组件
    • ChannelPipeline
    • ChannelHandler
    • ChannelHandlerContext
ChannelPipeline
  • ChannelPipeline负责组装各种ChannelHandler,实际数据的操作交由ChannelHandler进行处理完成。ChannelPipeline可以理解为ChannelHandler的实例列表–内部通过双向链表将ChannelHandler链接在一起
  • ChannelPipeline是线程安全的,因为每一个新的Channel都会对应绑定一个新的ChannelPipeline。一个ChannelPipeline关联一个EventLoop,一个EventLoop仅会绑定一个线程
ChannelHandler & ChannelHandlerContext
  • 没创建一个Channel就会绑定一个新的ChannelPipeline,ChannelPipeline中每加入一个ChannelHandler都会绑定一个ChannelHandlerContext。那么为何要绑定呢
  • ChannelHandlerContext用于保存ChannelHandler上下文,通过ChannelHandlerContext
    • 可以知道ChannelPipeline和ChannelHandler的关联关系(源码看看)
    • ChannelHandlerContext可以实现ChannelHandler之间的交互
    • ChannelHandlerContext包含了ChannelHandler生命周期的所有时间,如connect,bind,read等等

第三节、客户端和服务端启动都干了啥???

基本案例

  • 流程分为:
    • 创建引导器
    • 配置线程模型
    • 通过引导器绑定业务逻辑处理器
    • 配置一些网络参数
    • 绑定端口
  • 代码
    • 启动类
    public class HttpServer {
    
        public void start(int port) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
    
                //1. 配置线程池
                b.group(bossGroup, workerGroup)
    
                        //2. 设置Channel
                        //2.1 设置Channel类型
                        .channel(NioServerSocketChannel.class)
                        .localAddress(new InetSocketAddress(port))
    
                        //2.2 设置ChannelPipeline和ChannelHandler
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) {
                                ch.pipeline()
                                        .addLast("codec", new HttpServerCodec())                  // HTTP 编解码
                                        .addLast("compressor", new HttpContentCompressor())       // HttpContent 压缩
                                        .addLast("aggregator", new HttpObjectAggregator(65536))   // HTTP 消息聚合
                                        .addLast("handler", new HttpServerHandler());             // 自定义业务逻辑处理器
                            }
                        })
                        .childOption(ChannelOption.SO_KEEPALIVE, true);
    
                //3. 绑定端口
                ChannelFuture f = b.bind().sync();
                System.out.println("Http Server started, Listening on " + port);
                f.channel().closeFuture().sync();
            } finally {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
        }
        public static void main(String[] args) throws Exception {
            new HttpServer().start(8088);
        }
    }
    
    • 逻辑处理类
    public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
            String content = String.format("Receive http request,uri:%s,method:%s,content:%s%n",msg.getUri(),msg.getMethod(),msg.content().toString(CharsetUtil.UTF_8));
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.wrappedBuffer(content.getBytes()));
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }
    

Netty服务端启动

  • 启动三个步骤
    • 配置线程池
    • Channel初始化
    • 端口绑定
配置线程池
单线程模式
  • Reactor单线程模型所有I/O操作都由一个线程完成,所以只需要启动一个EventLoopGroup即可
  • 代码
    EventLoopGroup group = new NioEventLoopGroup(1);
    ServerBootstrap b = new ServerBootstrap();
    b.group(group)
    
  • 问题:
    • 所有I/O都是一个线程完成,所以单线程模型有非常严重的性能瓶颈
多线程模型
  • 因为Reactor单线程存在问题,那么多线程就出现了,在Netty中使用Reactor多线程模型与单线程模型非常相似,区别在于NioEventLoopGroup可以不需要任何参数,他默认会启动2倍的CPU核数的线程,当然,也可以自定义线程数
  • 代码
    EventLoopGroup group = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap();
    b.group(group)
    
主从多线程模式
  • 主从多线程Reactor模型。Boss是主Reactor,Worker是从Reactor。他们分别使用不同的NioEventLoopGroup,主Reactor负责处理Accept,然后把Channel注册到从Reactor上,从Reactor主要负责Channel生命周期内的所有I/O事件。
  • 代码
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)
    
Channel初始化
设置Channel类型
  • NIO模型是Netty中最成熟且被广泛使用的模型,因此,推荐Netty服务端小勇NioServerSocketChannel作为Channel类型,客户端采用NioSocketChannel。
注册ChannelHandler
  • Netty中通过ChannelPipeline去注册多个ChannelHandler,每个ChannelHandler各司其职,这样就可以实现最大化的代码复用
  • 如何通过引导器添加多个ChannelHandler或者说是,如何添加ChannelPipeline呢???
    • Channel初始化时都会绑定一个Pipeline,它主要用于服务编排。Pipeline管理了多个ChannelHandler。I/O事件依次在ChannelHandler中传播,ChannelHandler负责业务逻辑处理。
设置Channel参数
端口绑定
  • 代码:bind方法会真正触发启动,sync方法则会阻塞,直至整个启动过程完成
    ChannelFuture f = b.bind().sync();
    
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页