美文网首页vert.x & netty
Netty系列(1)---基础知识

Netty系列(1)---基础知识

作者: suxin1932 | 来源:发表于2019-07-27 10:47 被阅读0次

    https://www.cnblogs.com/imstudy/p/9908791.html (核心参考)
    https://www.bilibili.com/video/av59683486/?p=4 (核心参考)

    1.基本概念

    #摘自官网
    Netty is an asynchronous event-driven network application framework 
    for rapid development of maintainable high performance protocol servers & clients. 
    
    Netty .png

    1.1 BIO/NIO/AIO

    https://www.jianshu.com/p/f34bb3169443 (参考此文)

    1.1 Reactor 线程模型

    无论是C++还是Java编写的网络框架,大多数都是基于Reactor模式进行设计和开发,
    Reactor模式基于事件驱动,特别适合处理海量的I/O事件。
    
    #Reactor 模型
    是指通过一个或多个输入同时传递给服务器的服务请求的事件驱动处理模式。
    服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,
    Reactor 模式也叫 Dispatcher 模式,即 I/O 多了复用统一监听事件,
    收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术之一。
    
    #Reactor 模型中有 2 个关键组成
    1)Reactor:
    Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。
    它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人。
    2)Handlers:
    处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。
    Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。
    
    #取决于 Reactor 的数量和 Hanndler 线程数量的不同,Reactor 模型有 3 个变种:
    1)单 Reactor 单线程;
    2)单 Reactor 多线程;
    3)主从 Reactor 多线程。
    
    可以这样理解,Reactor 就是一个执行 
    while (true) { selector.select(); …} 循环的线程,
    会源源不断的产生新的事件,称作反应堆很贴切。
    

    1.1.1 Reactor单线程模型

    Reactor单线程模型,指的是所有的IO操作都在同一个NIO线程上面完成,
    在这个单线程中要负责接收请求,处理IO,编解码所有操作,
    相当于一个饭馆只有一个人,同时负责前台和后台服务,效率低。
    
    Reactor单线程模型的这个NIO线程的职责如下:
    1)作为NIO服务端,接收客户端的TCP连接;
    2)作为NIO客户端,向服务端发起TCP连接;
    3)读取通信对端的请求或者应答消息;
    4)向通信对端发送消息请求或者应答消息。
    
    #server 端示例:
    ServerBootstrap server = new ServerBootstrap();
    // 1.当 worker 不存在, server.group(boss, boss) 是 Reactor 的单线程模型
    NioEventLoopGroup reactorGroup = new NioEventLoopGroup(new DefaultThreadFactory("reactorGroup", true));
    server.group(reactorGroup, reactorGroup) ...
    
    Reactor单线程模型.png

    1.1.2 多线程模型

    多线程的优点在于有"单独的一个线程去处理请求","另外有一个线程池创建多个NIO线程去处理IO"。
    相当于一个饭馆有一个前台负责接待,有很多服务员去做后面的工作。
    
    #Reactor多线程模型的特点:
    1)有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP连接请求;
    2)网络IO操作-读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,
    它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
    3)1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。
    
    #server 端示例:
    ServerBootstrap server = new ServerBootstrap();
    // 2.当 worker 存在, boss 的 nThreads == 1 时,  server.group(boss, worker) 是 Reactor 的多线程模型
    NioEventLoopGroup acceptorGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("bossGroupExecutor", true));
    NioEventLoopGroup ioGroup = new NioEventLoopGroup(new DefaultThreadFactory("bossGroupExecutor", true));
    server.group(acceptorGroup, ioGroup) ...
    
    在绝大多数场景下,Reactor多线程模型都可以满足性能需求;
    但是,在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。
    例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。
    
    Reactor多线程模型.png

    1.1.3 Reactor主从线程模型

    多线程模型的缺点在于并发量很高的情况下,只有一个Reactor单线程去处理是来不及的,
    就像饭馆只有一个前台接待很多客人也是不够的。为此需要使用主从线程模型。
    >> 主从线程模型:"一组线程池接收请求","一组线程池处理IO"。
    
    #主从Reactor线程模型的特点是:
    1)MainReactor(NIO线程池1)负责客户端的连接请求,并将请求转交给 SubReactor;
    2)SubReactor(NIO线程池2)负责相应通道的 IO 读写请求;
    3)非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理。
    
    #server 端示例:
    ServerBootstrap server = new ServerBootstrap();
    // 3.当 worker 存在, boss 的 nThreads > 1 时,  server.group(boss, worker) 是 Reactor 的主从线程模型
    NioEventLoopGroup acceptorGroup = new NioEventLoopGroup(new DefaultThreadFactory("bossGroupExecutor", true));
    NioEventLoopGroup ioGroup = new NioEventLoopGroup(new DefaultThreadFactory("bossGroupExecutor", true));
    server.group(acceptorGroup, ioGroup) ...
    
    Reactor主从线程模型.png

    2.Netty中的相关组件

    2.1 NioEventLoop

    2.1.1 Bootstrap、ServerBootstrap

    Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,
    主要作用是配置整个 Netty 程序,串联各个组件,Netty 中:
    Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。
    

    2.1.2 Selector

    Netty 基于 Selector 对象实现 I/O 多路复用,通过Selector, 一个线程可以监听多个连接的 Channel 事件。
    
    当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以
    自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),
    这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。
    

    2.1.3 NioEventLoop & NioEventLoopGroup

    #NioEventLoop 
    主要方法是run(),是整个Netty执行过程的逻辑代码实现。
    NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,
    线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:
    >> I/O 任务,即 selectionKey 中 ready 的事件,如
    accept、connect、read、write 等,由 processSelectedKeys 方法触发。
    >> 非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。
    两种任务的执行时间比由变量 ioRatio 控制,默认为 50,
    则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等。
    
    Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端连接。
    当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
    线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
    由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
    一个 I/O 线程可以并发处理 N 个客户端连接和读写操作这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
    
    #NioEventLoopGroup
    NioEventLoopGroup,主要管理 eventLoop 的生命周期,
    可以理解为一个线程池,内部维护了一组线程,
    每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。
    主要方法是newChild,EventLoop的工厂类。EventLoopGroup.newChild创建EventLoop对象。
    OioEventLoopGroup除外,它没有实现newChild方法,调用父类的并创建ThreadPerChannelEventLoop对象。
    
    #EventExecutorGroup
    线程池实现,主要成员是children数组,主要方法是next(),获得线程池中的一个线程,由子类调用。
    由于Oio采用的是Thread per Channel机制,所以没有实现前面两个。
    
    #EventExecutor
    Task的执行类,主要成员是taskQueue以及真正的运行线程对象executor,
    主要方法是taskQueue操作方法execute、takeTask、addTask等,以及doStartThread方法。
    

    2.2 Channel, ChannelPipeline, ChannelHandlerContext

    2.2.1 io.netty.channel.Channel

    A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind.
    A channel provides a user:
     * <li>the current state of the channel (e.g. is it open? is it connected?),</li>
     * <li>the {@linkplain ChannelConfig configuration parameters} of the channel (e.g. receive buffer size),</li>
     * <li>the I/O operations that the channel supports (e.g. read, write, connect, and bind), and</li>
     * <li>the {@link ChannelPipeline} which handles all I/O events and requests associated with the channel.</li>
    
    每个 client 都会与 server 建立一个连接, 抽象为 channel.
    
    #常见实现类
    >> NioSocketChannel,异步的客户端 TCP Socket 连接。
    >> NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
    >> NioDatagramChannel,异步的 UDP 连接。
    >> NioSctpChannel,异步的客户端 Sctp 连接。
    >> NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
    >> OioSocketChannel, 同步的客户端 TCP Socket 连接.
    >> OioServerSocketChannel, 同步的服务器端 TCP Socket 连接.
    >> OioDatagramChannel, 同步的 UDP 连接
    >> OioSctpChannel, 同步的 Sctp 服务器端连接.
    >> OioSctpServerChannel, 同步的客户端 TCP Socket 连接.
    

    2.2.2 ChannelPipeline(默认实现是: DefaultChannelPipeline)

    ------------ ------------ ------------ ------------
    ChannelPipeline 是一个与 Channel 所关联的容器(一一对应)
    其中存放的是一个一个的 ChannelHandlerContext 对象
    而 ChannelHandlerContext 里面存放的是一个一个的 ChannelHandler 对象
    ------------ ------------ ------------ ------------
    All I/O operations are asynchronous.
    All I/O operations in Netty are asynchronous.  It means any I/O calls will return immediately with no guarantee that the requested I/O operation has been completed at the end of the call.  
    Instead, you will be returned with a {@link ChannelFuture} instance which will notify you when the requested I/O operation has succeeded, failed, or canceled.
    
    #1.用到了'高级拦截过滤器'模式
    A list of {@link ChannelHandler}s which handles or intercepts inbound events and outbound operations of a {@link Channel}.  
    {@link ChannelPipeline} implements an advanced form of the <a href="http://www.oracle.com/technetwork/java/interceptingfilter-142169.html">Intercepting Filter</a> pattern to give a user full control over how an event is handled and how the {@link ChannelHandler}s in a pipeline interact with each other.
    
    #2.ChannelPipeline创建过程
    Each channel has its own pipeline and it is created automatically when a new channel is created.
    
    #3.事件在ChannelPipeline中的流动过程: 真正的处理是交给 ChannelHandler 来处理的
     * The following diagram describes how I/O events are processed by {@link ChannelHandler}s in a {@link ChannelPipeline}
     * typically. An I/O event is handled by either a {@link ChannelInboundHandler} or a {@link ChannelOutboundHandler}
     * and be forwarded to its closest handler by calling the event propagation methods defined in
     * {@link ChannelHandlerContext}, such as {@link ChannelHandlerContext#fireChannelRead(Object)} and
     * {@link ChannelHandlerContext#write(Object)}.
     *
     * <pre>
     *                                                 I/O Request
     *                                            via {@link Channel} or
     *                                        {@link ChannelHandlerContext}
     *                                                      |
     *  +---------------------------------------------------+---------------+
     *  |                           ChannelPipeline         |               |
     *  |                                                  \|/              |
     *  |    +---------------------+            +-----------+----------+    |
     *  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |              /|\                                  |               |
     *  |               |                                  \|/              |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |              /|\                                  .               |
     *  |               .                                   .               |
     *  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
     *  |        [ method call]                       [method call]         |
     *  |               .                                   .               |
     *  |               .                                  \|/              |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |              /|\                                  |               |
     *  |               |                                  \|/              |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
     *  |    +----------+----------+            +-----------+----------+    |
     *  |              /|\                                  |               |
     *  +---------------+-----------------------------------+---------------+
     *                  |                                  \|/
     *  +---------------+-----------------------------------+---------------+
     *  |               |                                   |               |
     *  |       [ Socket.read() ]                    [ Socket.write() ]     |
     *  |                                                                   |
     *  |  Netty Internal I/O Threads (Transport Implementation)            |
     *  +-------------------------------------------------------------------+
     * </pre>
    
    #4.如何将一个ChannelPipeline中的ChannelHandler传递给下一个ChannelHandler 
    #(这里在自定义 ChannelHandler时, 尤其重要!!!!!!!!!!!!!!!!)
     * As you might noticed in the diagram shows, a handler has to invoke the event propagation methods in
     * {@link ChannelHandlerContext} to forward an event to its next handler.  Those methods include:
     * <ul>
     * <li>Inbound event propagation methods:
     *     <ul>
     *     <li>{@link ChannelHandlerContext#fireChannelRegistered()}</li>
     *     <li>{@link ChannelHandlerContext#fireChannelActive()}</li>
     *     <li>{@link ChannelHandlerContext#fireChannelRead(Object)}</li>
     *     <li>{@link ChannelHandlerContext#fireChannelReadComplete()}</li>
     *     <li>{@link ChannelHandlerContext#fireExceptionCaught(Throwable)}</li>
     *     <li>{@link ChannelHandlerContext#fireUserEventTriggered(Object)}</li>
     *     <li>{@link ChannelHandlerContext#fireChannelWritabilityChanged()}</li>
     *     <li>{@link ChannelHandlerContext#fireChannelInactive()}</li>
     *     <li>{@link ChannelHandlerContext#fireChannelUnregistered()}</li>
     *     </ul>
     * </li>
     * <li>Outbound event propagation methods:
     *     <ul>
     *     <li>{@link ChannelHandlerContext#bind(SocketAddress, ChannelPromise)}</li>
     *     <li>{@link ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)}</li>
     *     <li>{@link ChannelHandlerContext#write(Object, ChannelPromise)}</li>
     *     <li>{@link ChannelHandlerContext#flush()}</li>
     *     <li>{@link ChannelHandlerContext#read()}</li>
     *     <li>{@link ChannelHandlerContext#disconnect(ChannelPromise)}</li>
     *     <li>{@link ChannelHandlerContext#close(ChannelPromise)}</li>
     *     <li>{@link ChannelHandlerContext#deregister(ChannelPromise)}</li>
     *     </ul>
     * </li>
     * </ul>
    
    #5.当业务线程有耗时任务时, 需自定义业务线程池来实现
     * static final {@link EventExecutorGroup} group = new {@link DefaultEventExecutorGroup}(16);
     * ...
     *
     * {@link ChannelPipeline} pipeline = ch.pipeline();
     *
     * pipeline.addLast("decoder", new MyProtocolDecoder());
     * pipeline.addLast("encoder", new MyProtocolEncoder());
     *
     * // Tell the pipeline to run MyBusinessLogicHandler's event handler methods
     * // in a different thread than an I/O thread so that the I/O thread is not blocked by
     * // a time-consuming task.
     * // If your business logic is fully asynchronous or finished very quickly, you don't
     * // need to specify a group.
     * pipeline.addLast(group, "handler", new MyBusinessLogicHandler());
    
    #6.ChannelPipeline是线程安全的, 因此可动态增删 ChannelHandler
     * A {@link ChannelHandler} can be added or removed at any time because a {@link ChannelPipeline} is thread safe.
     * For example, you can insert an encryption handler when sensitive information is about to be exchanged, and remove it
     * after the exchange.
    

    2.2.3 ChannelHandlerContext (默认实现类: DefaultChannelHandlerContext)

    #基本情形
    底层是一个双向链表, 是 ChannelHandler 与 ChannelPipeline 之间交互的桥梁和纽带
    其中几个方法, 可以分别获取到 Channel 对象/ChannelHandler 对象/ ChannelPipeline对象:
    Channel channel();
    ChannelHandler handler();
    ChannelPipeline pipeline();
    从而构成了四大组件(前者包含后者):
    Channel-->ChannelPipeline-->ChannelHandlerContext-->ChannelHandler
    
    #事件在入站处理器之间传递的关键: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    自定义Handler时, 请评估是否需调用 firexxx 方法, 否则可能无法传递到下一个 ChannelHandler
    
    #事件的出站处理器之间传递的关键: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    自定义Handler时, 请评估是否需调用 write, read 等方法, 否则可能无法传递到下一个 ChannelHandler
    

    2.2.4 ChannelHandler (可类比成 filter)

    ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,
    并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。
    
    #主要分为两大类
    >> ChannelInboundHandler 用于处理入站 I/O 事件。
    >> ChannelOutboundHandler 用于处理出站 I/O 操作。
    
    #适配器类为
    >> ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。
    >> ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。
    >> ChannelDuplexHandler 用于处理入站和出站事件。
    
    #对于 Inbound 事件:
    >> Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层.
    >> Inbound 事件发起者是 unsafe
    >> Inbound 事件的处理者是 Channel, 如果用户没有实现自定义的处理方法, 
    那么Inbound 事件默认的处理者是 TailContext, 并且其处理方法是空实现.
    >> Inbound 事件在 Pipeline 中传输方向是 head -> tail
    >> 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 
    则需要调用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 将此事件继续传播下去. 
    !!!!!!!!!!!!!!!!!!!如果不这样做, 那么此事件的传播会提前终止.
    >> Inbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT
    
    #对于 Outbound事件:
    >> Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求)
    >> Outbound 事件的发起者是 Channel
    >> Outbound 事件的处理者是 unsafe
    >> Outbound 事件在 Pipeline 中的传输方向是 tail -> head.
    >> 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 
    则需要调用 ctx.xxx (例如 ctx.connect) 将此事件继续传播下去. 
    !!!!!!!!!!!!!!!!!!!如果不这样做, 那么此事件的传播会提前终止.
    >> Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT
    
    Channel-ChannelPipeline-ChannelHandlerContext-ChannelHandler.png
    #Channel-ChannelPipeline-ChannelHandlerContext-ChannelHandler
    >> 一个 Channel 包含了一个 ChannelPipeline,
    >> 一个 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,
    >> 一个 ChannelHandlerContext 中又关联着一个 ChannelHandler。
    >> 入站事件和出站事件在一个双向链表中,
    入站事件会从链表 head 往后传递到最后一个入站的 handler,
    出站事件会从链表 tail 往前传递到最前一个出站的 handler,
    两种类型的 handler 互不干扰。
    
    #以下述代码为例
    server.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringDecoder())...
            }
        });
    
    #ch.pipeline().addLast(new StringDecoder()) 构建过程为:
    --> DefaultChannelPipeline#addLast(String, ChannelHandler)
        --> DefaultChannelPipeline#addLast(EventExecutorGroup, String, ChannelHandler)
            #新建一个 ChannelHandlerContext
            --> DefaultChannelPipeline#newContext
                #将 ChannelHandler 作为 ChannelHandlerContext 构造器的一个入参维护
                --> DefaultChannelHandlerContext#DefaultChannelHandlerContext
            #维护 ChannelHandlerContext 的链表的前后节点
            --> DefaultChannelPipeline#addLast0
    

    2.2.5 ChannelOption

    是用于通过ChannelConfig, 配置与Channel相关的, 网络层相关的配置
    
    1、ChannelOption.SO_BACKLOG
    ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数,
    函数listen(int socketfd,int backlog)用来初始化服务端可连接队列,
    服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,
    多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
    服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128。
    
    2、ChannelOption.SO_REUSEADDR
    ChanneOption.SO_REUSEADDR对应于套接字选项中的SO_REUSEADDR,
    Socket参数,地址复用,默认值False。有四种情况可以使用:
    (1) 当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,
    而你希望启动的程序的socket2要占用该地址和端口,比如重启服务且保持先前端口。
    (2) 有多块网卡或用IP Alias技术的机器在同一端口启动多个进程,但每个进程绑定的本地IP地址不能相同。
    (3) 单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。
    (4) 完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。
    
    3、ChannelOption.SO_KEEPALIVE
    Channeloption.SO_KEEPALIVE参数对应于套接字选项中的SO_KEEPALIVE,
    该参数用于设置TCP连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。
    当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。
    Netty中一般关闭该选项, 而是使用IdleHandler 来实现.
    
    4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF
    >> ChannelOption.SO_SNDBUF参数对应于套接字选项中的SO_SNDBUF,
    发送缓冲区用于保存发送数据,直到发送成功。
    Socket参数,TCP数据发送缓冲区大小。
    该缓冲区即TCP发送滑动窗口,linux操作系统可使用命令:
    cat /proc/sys/net/ipv4/tcp_smem 查询其大小。
    >> ChannelOption.SO_RCVBUF参数对应于套接字选项中的SO_RCVBUF
    接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功。
    
    5、ChannelOption.SO_LINGER
    ChannelOption.SO_LINGER参数对应于套接字选项中的SO_LINGER,
    Socket参数,关闭Socket的延迟时间,默认值为-1,表示禁用该功能。
    >> -1表示socket.close()方法立即返回,但OS底层会将发送缓冲区全部发送到对端。
    >> 0表示socket.close()方法立即返回,OS放弃发送缓冲区的数据直接向对端发送RST包,对端收到复位错误。
    >> 非0整数值表示调用socket.close()方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。
    
    6、ChannelOption.TCP_NODELAY
    ChannelOption.TCP_NODELAY参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关。
    Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,
    因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,
    虽然该方式有效提高网络的有效负载,但是却造成了延时,
    
    这个参数,与是否开启Nagle算法是反着来的,true表示关闭,false表示开启。
    通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,
    如果需要减少发送次数减少网络交互,就开启。
    
    与TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。
    
    7、ALLOW_HALF_CLOSURE
    Netty参数,一个连接的远端关闭时本地端是否关闭,默认值为False。
    >> 为False时,连接自动关闭;
    >> 为True时,触发ChannelInboundHandler的userEventTriggered()方法,事件为ChannelInputShutdownEvent。
    
    8、SO_BROADCAST
    Socket参数,设置广播模式。
    

    2.2.6 其他

    #1.AttributeMap, AttributeKey, Attribute
    比如一个流程中, 有4个Handler, 在第二个Handler中加入了业务数据, 在第四个Handler中想要取出来进行处理, 可以采用Attribute
    主要维护业务运行数据, 在运行时候, 向程序中动态添加业务数据
    ChannelHandlerContext 与 Channel 都继承了 AttributeMap接口
    ChannelHandlerContext.attr(..) == Channel.attr(..) // 4.1开始, 之前的版本不等
    4.1版本后, 若想使得key不重复, 可以在自己的 ChannelHandler 中定义 private static final AttributeKey
    
    #2.ChannelConfig
    class Channel {
        private ChannelConfig config;
        ....... attribute
    }
    

    2.3 Netty中的Future, ChannelFuture, ChannelPromise

    #异步读写操作的架构思想及观察者模式的应用
    当一个异步过程调用发出后,调用者不能立刻得到结果。
    实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
    
    Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。
    调用者并不能立刻获得结果,而是通过 Future-Listener 机制,
    用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
    
    当 Future 对象刚刚创建时,处于非完成状态,
    调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作。
    

    3.Netty中的一些设计模式

    #1.适配器模式
    ChannelHandlerAdapter 
    ChannelInboundHandler 
    ChannelInboundHandlerAdapter 
    
    #2.观察者模式
    Future, ChannelFuture, ChannelPromise
    
    #3.模板模式
    SimpleChannelInboundHandler (其有一abstract方法供子类实现)
    

    4.Netty的实用性

    4.1 应用领域

    1)互联网行业:
    在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,
    Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。
    如阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,
    Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。
    
    2)游戏行业:
    无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用。
    Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈。
    非常方便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以方便的通过 Netty 进行高性能的通信。
    
    3)大数据领域:
    经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,
    默认采用 Netty 进行跨界点通信,它的 Netty Service 基于 Netty 框架二次封装实现。
    

    https://netty.io/wiki/related-projects.html (Netty相关工程)

    4.2 实用功能

    开发异步、非阻塞的 TCP 网络应用程序;
    开发异步、非阻塞的 UDP 网络应用程序;
    开发异步文件传输应用程序;
    开发异步 HTTP 服务端和客户端应用程序;
    提供对多种编解码框架的集成,包括谷歌的 Protobuf、Jbossmarshalling、Java 序列化、压缩编解码、XML 解码、字符串编解码等,这些编解码框架可以被用户直接使用;
    提供形式多样的编解码基础类库,可以非常方便的实现私有协议栈编解码框架的二次定制和开发;
    基于职责链模式的 Pipeline-Handler 机制,用户可以非常方便的对网络事件进行拦截和定制;
    所有的 IO 操作都是异步的,用户可以通过 Future-Listener 机制主动 Get 结果或者由 IO 线程操作完成之后主动 Notify 结果,用户的业务线程不需要同步等待;
    IP 黑白名单控制;
    打印消息码流;
    流量控制和整形;
    性能统计;
    基于链路空闲事件检测的心跳检测
    ……
    

    参考资源
    https://netty.io/
    https://developers.google.cn/protocol-buffers/
    https://www.grpc.io/
    李林锋. (2015). Netty权威指南(第2版).
    http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf (Scalable IO in Java)

    相关文章

      网友评论

        本文标题:Netty系列(1)---基础知识

        本文链接:https://www.haomeiwen.com/subject/xbmsrctx.html