1.netty底层实现原理

  1.netty:高性能、高并发网络应用框架。

  2.如何做到高性能?高并发?

    1.对NIO进行封装。

      1.NIO:基于事件驱动,(单线程模型,多线程模型,主从模型)

  3.selector:请求-->socket Channel-->selector(thread-->关注io读写事件)

    1.Channel(它负责基本的 IO 操作,例如:bind(),connect(),read(),write() 等等。同时它也是数据入站和出站的载体)

    2.EventLoop聚合了多路复用器Selector。消息的“出站”/“入站”就称为事件(Event)。

    3.EventLoopGroup 要做的就是创建一个新的 Channel,并且生成一个EventLoop并分配给它。

    4.ChannelHandler:EventLoop 是事件的通知者,那么 ChannelHandler 就是事件的处理者

    5.ChannelPipeline:事件的处理顺序是由 ChannelPipeline 来决定的。到 Channel 被创建的时候,ChannelHandler 会被 Netty 框架自动分配到 ChannelPipeline 上。

    6.ChannelHandlerContext:负责传递消息,每当有 ChannelHandler 添加到 ChannelPipeline 时,同时会创建 ChannelHandlerContext..

  4.Netty 的数据容器: 

  5.Bootstrap 对象:Bootstrap 的作用就是将 Netty 核心组件配置到程序中,并且让他们运行起来。

   

  优秀原文链接:https://blog.csdn.net/suifeng629/article/details/103500016

  1.netty介绍:

    1.定义:Netty 是一个异步的、基于事件驱动的网络应用框架,它可以用来开发高性能服务端和客户端。(实际并非异步)

    2.为了满足高并发下网络请求,引入了 NIO 的概念。Netty 是针对 NIO 的实现,在 NIO 封装,网络调用,数据处理以及性能优化等方面都有不俗的表现。

    3.Selector 机制就是 NIO 的核心。

      1.当每次客户端请求时,会创建一个 Socket Channel,并将其注册到 Selector 上(多路复用器);

      2.Selector 关注服务端 IO 读写事件,此时客户端并不用等待 IO 事件完成,可以继续做接下来的工作;

      3.一旦,服务端完成了 IO 读写操作,Selector 会接到通知,同时告诉客户端 IO 操作已经完成;

      4.接到通知的客户端,就可以通过 SocketChannel 获取需要的数据了。

      

  2.netty的优点:

    1.对 NIO 进行封装,开发者不需要关注 NIO 的底层原理,只需要调用 Netty 组件就能够完成工作。

    2.对网络调用透明,从 Socket 建立 TCP 连接到网络异常的处理都做了包装。

    3.对数据处理灵活, Netty 支持多种序列化框架,通过“ChannelHandler”机制,可以自定义“编/解码器”。

    4.对性能调优友好,Netty 提供了线程池模式以及 Buffer 的重用机制(对象池化),不需要构建复杂的多线程模型和操作队列。

  3.netty场景:

    1.Netty 作为 NIO 的实现,它适用于服务器/客户端通讯的场景;

    2.以及针对于 TCP 协议下的高并发应用。

2.netty核心组件:

  1.Channel

    1.当客户端和服务端连接的时候会建立一个 Channel。

    2.这个 Channel 我们可以理解为 Socket 连接,它负责基本的 IO 操作,例如:bind(),connect(),read(),write() 等等。

    3.简单的说,Channel 就是代表连接,同时它也是数据入站和出站的载体。

  2.EventLoop 和 EventLoopGroup

    1.EventLoop:消息的“出站”/“入站”就会产生事件(Event),那么就有一个机制去监控和协调事件。这个机制就是EventLoop。Netty的IO线程EventLoop聚合了多路复用器Selector。

    2.在 Netty 中每个 Channel 都会被分配到一个 EventLoop。

      1.一个 EventLoop 可以服务于多个 Channel。

      2.每个 EventLoop 会占用一个 Thread,同时这个 Thread 会处理 EventLoop 上面发生的所有 IO 操作和事件。

    3.图形展示:(channel1、channel2、channel3)-eventloop-thread-(io,io,io)

    

 

    4.EventLoopGroup :

      1.EventLoopGroup 要做的就是创建一个新的 Channel,并且生成一个EventLoop并分配给它。

      2.一个 EventLoopGroup 中包含了多个 EventLoop 对象。

      

 

       3.模拟请求流程

        1.客户端发送消息到服务端,EventLoop 发现以后会告诉服务端:“你去获取消息”,同时客户端进行其他的工作。

        2.当 EventLoop 检测到服务端返回的消息,也会通知客户端:“消息返回了,你去取吧“,客户端再去获取消息。

        3.整个过程 EventLoop 就是监视器+传声筒。

  3.ChannelHandler,ChannelPipeline 和 ChannelHandlerContext

    1.ChannelHandler:EventLoop 是事件的通知者,那么 ChannelHandler 就是事件的处理者

      1.ChannelHandler 中可以添加一些业务代码,例如数据转换,逻辑运算等等。

      2.Server 和 Client 分别都有一个 ChannelHandler 来处理,读取信息,网络可用,网络异常之类的信息。

      3.针对出站和入站的事件,有不同的 ChannelHandler。

        1.ChannelInBoundHandler(入站事件处理器)

        2.ChannelOutBoundHandler(出站事件处理器)

      

    2.ChannelPipeline :事件的处理顺序是由 ChannelPipeline 来决定的。

    

 

       1.ChannelPipeline 为 ChannelHandler 链提供了容器。到 Channel 被创建的时候,会被 Netty 框架自动分配到 ChannelPipeline 上。

       2.ChannelPipeline 保证 ChannelHandler 按照一定顺序处理事件,当事件触发以后,会将数据通过 ChannelPipeline 按照一定的顺序通过 ChannelHandler。

       3.ChannelPipeline 也可以添加或者删除 ChannelHandler,管理整个队列。

    3.ChannelHandlerContext :每当有 ChannelHandler 添加到 ChannelPipeline 时,同时会创建 ChannelHandlerContext.

      1.ChannelHandlerContext 的主要功能是管理 ChannelHandler 和 ChannelPipeline 的交互,ChannelHandlerContext 负责传递消息.

      2.ChannelHandlerContext 参数贯穿 ChannelPipeline,将信息传递给每个 ChannelHandler。如下图:

      

 

  4.netty核心组件关系图:

    

 

  5.Netty 的数据容器:

    1.ByteBuf 工作原理:

      1.从结构上来说,ByteBuf 由一串字节数组构成。数组中每个字节用来存放信息。

      2.ByteBuf 提供了两个索引,一个用于读取数据,一个用于写入数据。这两个索引通过在字节数组中移动,来定位需要读或者写信息的位置。

      3.如果 readerIndex 超过了 writerIndex 的时候,Netty 会抛出 IndexOutOf-BoundsException 异常。

    2.ByteBuf 使用模式:

      1.堆缓冲区:

        1.ByteBuf 将数据存储在 JVM 的堆中,通过数组实现,可以做到快速分配。

        2.由于在堆上被 JVM 管理,在不被使用时可以快速释放。可以通过 ByteBuf.array() 来获取 byte[] 数据。

      2.直接缓冲区:

        1.在 JVM 的堆之外直接分配内存,用来存储数据。其不占用堆空间,使用时需要考虑内存容量。

        2.它在使用 Socket 传递时性能较好,因为间接从缓冲区发送数据,在发送之前 JVM 会先将数据复制到直接缓冲区再进行发送。

        3.直接缓冲区的数据分配在堆之外,通过 JVM 进行垃圾回收,并且分配时也需要做复制的操作,因此使用成本较高。

      3.复合缓冲区:

        1.顾名思义就是将上述两类缓冲区聚合在一起。Netty 提供了一个 CompsiteByteBuf,可以将堆缓冲区和直接缓冲区的数据放在一起,让使用更加方便。

    3.ByteBuf 的分配

      Netty 提供了两种 ByteBufAllocator 的实现

      1.PooledByteBufAllocator,实现了 ByteBuf 的对象的池化,提高性能减少内存碎片。

        1.对象池化的技术和线程池,比较相似,目的是提高内存的使用率。

        2.池化技术简单实现思路:是在 JVM 堆内存上构建一层内存池,通过 allocate 方法获取内存池中的空间,通过 release 方法将空间归还给内存池。

        3.池化技术算法优化:伙伴系统

          1.用完全二叉树管理内存区域,左右节点互为伙伴,每个节点代表一个内存块。内存分配将大块内存不断二分,直到找到满足所需的最小内存分片。

          2.内存释放会判断释放内存分片的伙伴(左右节点)是否空闲,如果空闲则将左右节点合成更大块内存。

      2.Unpooled-ByteBufAllocator,没有实现对象的池化,每次会生成新的对象实例。

        1.对象的生成和销毁,会大量地调用 allocate 和 release 方法,因此内存池面临碎片空间回收的问题,在频繁申请和释放空间后,内存池需要保证连续的内存空间,用于对象的分配。

        2.对象生成和销毁算法优化:slab 系统,主要解决内存碎片问题,将大块内存按照一定内存大小进行等分,形成相等大小的内存片构成的内存集。

          1.按照内存申请空间的大小,申请尽量小块内存或者其整数倍的内存,释放内存时,也是将内存分片归还给内存集。

  6.Bootstrap 对象

    1.描述:Bootstrap 的作用就是将 Netty 核心组件配置到程序中,并且让他们运行起来。

    2.结构:从 Bootstrap 的继承结构来看,Bootstrap 和 ServerBootstrap,一个对应客户端的引导,另一个对应服务端的引导

    

 

    3.客户端引导 Bootstrap:

      主要有两个方法 bind() 和 connect()。

      1.Bootstrap 通过 bind() 方法创建一个 Channel。

      2.在 bind() 之后,通过调用 connect() 方法来创建 Channel 连接。

      3.Bootstrap(客户端引导)只要知道服务端 IP 和 Port 建立连接就可以了.

      4.Bootstrap(客户端引导)需要一个 EventLoopGroup.

    4.服务端引导 ServerBootstrap:

      1.与客户端不同的是在 Bind() 方法之后会创建一个 ServerChannel;

      2.这个ServerChannel不仅会创建新的 Channel 还会管理已经存在的 Channel。

      3.ServerBootstrap(服务端引导)绑定一个端口,用来监听客户端的连接请求.

      4.ServerBootstrap(服务端引导)则需要两个 EventLoopGroup.

        因为服务器需要两组不同的 Channel.

        1.第一组 ServerChannel 自身监听本地端口的套接字。

        2.第二组用来监听客户端请求的套接字。

      

3.netty调用流程,代码实现

  假设有一个客户端去调用一个服务端,假设服务端叫做 EchoServer,客户端叫做 EchoClient,用 Netty 架构实现代码如下。

  1.服务端代码:

    1.构建服务器端,假设服务器接受客户端传来的信息。在构造函数中传入需要监听的端口号。

      

 

    2.接下来就是服务的启动方法:

      1.启动 NettyServer 的 Start 方法

      

      2.方法做了如下事:

       1.创建 EventLoopGroup。

       2.创建 ServerBootstrap。

       3.指定所使用的 NIO 传输 Channel。

       4.使用指定的端口设置套接字地址。

       5.添加一个 ServerHandler 到 Channel 的 ChannelPipeline。

       6.异步地绑定服务器;调用 sync() 方法阻塞等待直到绑定完成。

       7.获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成。

       8.关闭 EventLoopGroup,释放所有的资源。

    3.NettyServer 启动以后会监听某个端口的请求,当接受到了请求就需要处理了。

       1.可以通过 ChannelInboundHandlerAdapter 实现

      

 

 

       2.上图这三个方法都是根据事件触发的

        1.当接收到消息时的操作,channelRead。

        2.消息读取完成时的方法,channelReadComplete。

        3.出现异常时的方法,exceptionCaught。

  2.客户端代码:

    1.客户端和服务端的代码基本相似,在初始化时需要输入服务端的 IP 和 Port。如下图

    

     2.同样在客户端启动函数中包括以下内容:如下图

    

       1.上图客户端启动程序的顺序如下:

        1.创建 Bootstrap。

        2.指定 EventLoopGroup 用来监听事件。

        3.定义 Channel 的传输模式为 NIO(Non-BlockingInputOutput)。

        4.设置服务器的 InetSocketAddress。

        5.在创建 Channel 时,向 ChannelPipeline 中添加一个 EchoClientHandler 实例。

        6.连接到远程节点,阻塞等待直到连接完成。

        7.阻塞,直到 Channel 关闭。

        8.关闭线程池并且释放所有的资源。

    3.客户端在完成以上操作以后,会与服务端建立连接从而传输数据。同样在接受到 Channel 中触发的事件时,客户端会触发对应事件的操作。

    

4.io/nio/bio/reactor(多路复用模式)/netty的区别:

原文网址:https://www.cnblogs.com/crazymakercircle/p/10225159.html

  java IO读写的底层流程:

  

 

  阻塞io:

    1.含义:指的是需要内核IO操作彻底完成后,才返回到用户空间,执行用户的操作.

    2.描述:阻塞指的是用户空间程序的执行状态,用户空间程序需等到IO操作彻底完成。

  非阻塞io:

    1.含义:指的是用户程序不需要等待内核IO操作完成后,内核立即返回给用户一个状态值,用户空间无需等到内核的IO操作彻底完成,可以立即返回用户空间。

    2.描述:执行用户的操作,处于非阻塞的状态。

 

   1.同步阻塞io(blocking io)-bio:

    1.优点:程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。

    2.缺点:BIO模型在高并发场景下是不可用的。

  2.同步非阻塞io(Non-blocking IO)-nio:

    1.优点:每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。

    2.缺点:需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。NIO模型在高并发场景下,也是不可用的。

  3.io多路复用(io Multiplexing):经典的Reactor设计模式。

    1.异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

    2.目前支持IO多路复用的系统调用有 select,epoll等等。

    3.基本原理:IO多路复用模型的基本原理就是select/epoll系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。

    4.优点:用select/epoll的优势在于,它可以同时处理成千上万个连接(connection),系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。

 

  4.异步Io:

    1.描述:有点类似于Java中比较典型的模式是回调模式。

  5.select/poll/epoll的区别: 

    原文链接:https://blog.csdn.net/h2604396739/article/details/82534253

    三种方式区别

    1..select的几大缺点:

      1.每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;

      2.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;

      3.select支持的文件描述符数量太小了,默认是1024.  

    2.poll实现
      poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构.

    3.epoll(reference Link)
      epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,                epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

  6.Java的NIO(new io)技术,使用的就是IO多路复用模型,在linux系统上,使用的是epoll系统调用。

    1.new io原理:https://www.cnblogs.com/funyoung/p/11810581.html

      1.NIO主要有三个核心部分:Selector、Channel、Buffer

      2.数据总是从Channel读取到Buffer或者从Buffer写入到Channel中。

      3.Selector可以监听多个Channel的多个事件。

    2.传统的IO与Channel的区别:

       1.传统的IO是BIO的,而Channel是NIO的。

          *当流调用了read()、write()方法后会一直阻塞线程直到数据被读取或写入完毕。

       2.传统IO流是单向的,而Channel是双向的。

  7.Reactor模式原理:

    1.Reactor模式是在NIO下实现的

    2.Reactor有三种模式:

      1.Reactor单线程模式

      2.Reactor多线程模式

      3.主从Reactor多线程模式