当前位置: 首页 > news >正文

Netty-Netty组件了解

EventLoop EventLoopGroup

        回想一下我们在 NIO 中是如何处理我们关心的事件的?在一个 while 循环中 select 出事
件,然后依次处理每种事件。我们可以把它称为事件循环,这就是 EventLoop interface
io.netty.channel. EventLoop 定义了 Netty 的核心抽象,用于处理网络连接的生命周期中所发
生的事件。
        io.netty.util.concurrent 包构建在 JDK java.util.concurrent 包上。 而 io.netty.channel 包
中的类,为了与 Channel 的事件进行交互,扩展了这些接口/类。 一个 EventLoop 将由一个
永远都不会改变的 Thread 驱动,同时任务( Runnable 或者 Callable )可以直接提交给
EventLoop 实现,以立即执行或者调度执行。

线程的分配

         服务于 Channel 的 I/O 和事件的 EventLoop 包含在 EventLoopGroup 中。
        异步传输实现只使用了少量的 EventLoop (以及和它们相关联的 Thread ),而且在当前
的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread
支撑大量的 Channel ,而不是每个 Channel 分配一个 Thread EventLoopGroup 负责为每个
新创建的 Channel 分配一个 EventLoop 在当前实现中,使用顺序循环(round-robin)的方
式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel
        一旦一个 Channel 被分配给一个 EventLoop ,它将在它的整个生命周期中都使用这个
EventLoop (以及相关联的 Thread )。

        需要注意,EventLoop 的分配方式对 ThreadLocal 的使用的影响。因为一个 EventLoop
常会被用于支撑多个 Channel ,所以对于所有相关联的 Channel 来说, ThreadLocal 都将是
一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上
下文中,它仍然可以被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚
至是事件。

线程管理

         在内部,当提交任务到如果当前)调用线程正是支撑 EventLoop 的线程,那么所提交
的代码块将会被(直接)执行。 否则, EventLoop 将调度该任务以便稍后执行,并将它放入
到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务 / 事件。

ChannelEventLoop(Group)ChannelFuture

         Netty 网络抽象的代表:
        Channel—Socket;
        EventLoop—控制流、多线程处理、并发;
        ChannelFuture—异步通知。
        Channel 和 EventLoop 关系如图
        从图上我们可以看出 Channel 需要被注册到某个 EventLoop 上,在 Channel 整个生命周
期内都由这个EventLoop处理IO事件,也就是说一个Channel和一个EventLoop进行了绑定,
但是一个EventLoop可以同时被多个Channel绑定。 这一点在“ EventLoop EventLoopGroup
节里也提及过。

Channel 接口

        基本的 I/O 操作( bind() connect() read() write() )依赖于底层网络传输所提供的原
语。在基于 Java 的网络编程中,其基本的构造是类 Socket Netty Channel 接口所提供
API ,被用于所有的 I/O 操作。大大地降低了直接使用 Socket 类的复杂性。此外, Channel
也是拥有许多预定义的、专门化实现的广泛类层次结构的根。
        由于 Channel 是独一无二的,所以为了保证顺序将 Channel 声明为 java.lang.Comparable
的一个子接口。因此,如果两个不同的 Channel 实例都返回了相同的散列码,那么
AbstractChannel 中的 compareTo() 方法的实现将会抛出一个 Error
Channel 的生命周期状态
         ChannelUnregistered : Channel 已经被创建,但还未注册到 EventLoop
        ChannelRegistered : Channel 已经被注册到了 EventLoop
         ChannelActive : Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接
收和发送数据了
         ChannelInactive : Channel 没有连接到远程节点
         当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给 ChannelPipeline
中的 ChannelHandler,其可以随后对它们做出响应。在我们的编程中,关注 ChannelActive 和
ChannelInactive 会更多一些。
重要 Channel 的方法
         eventLoop: 返回分配给 Channel EventLoop
         pipeline: 返回 Channel ChannelPipeline ,也就是说每个 Channel 都有自己的
ChannelPipeline
         isActive: 如果 Channel 是活动的,则返回 true 。活动的意义可能依赖于底层的传输。
例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被
打开便是活动的。
         localAddress: 返回本地的 SokcetAddress
         remoteAddress: 返回远程的 SocketAddress
         write: 将数据写到远程节点,注意,这个写只是写往 Netty 内部的缓存,还没有真正
写往 socket
         flush: 将之前已写的数据冲刷到底层 socket 进行传输。
         writeAndFlush: 一个简便的方法,等同于调用 write() 并接着调用 flush()

ChannelPipeline ChannelHandlerContext

ChannelPipeline 接口

        当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline ,每个 Channel
有自己的 ChannelPipeline 。这项关联是永久性的。在 Netty 组件的生命周期中,这是一项固
定的操作,不需要开发人员的任何干预。
        ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播 入站(也
就是从网络到业务处理) 出站(也就是从业务处理到网络) ,各种事件流的 API ,我们
代码中的 ChannelHandler 都是放在 ChannelPipeline 中的。
        使得事件流经 ChannelPipeline ChannelHandler 的工作,它们是在应用程序的初始化
或者引导阶段被安装的。这些 ChannelHandler 对象接收事件、执行它们所实现的处理逻辑,
并将数据传递给链中的下一个 ChannelHandler ,而且 ChannelHandler 对象也完全可以拦截
事件不让事件继续传递。它们的执行顺序是由它们被添加的顺序所决定的。

ChannelHandler 的生命周期

        在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调
用下面这些方法。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。
handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught 当处理过程中在 ChannelPipeline 中有错误产生时被调用

ChannelPipeline 中的 ChannelHandler

         入站和出站 ChannelHandler 被安装到同一个 ChannelPipeline 中,ChannelPipeline 以双
向链表的形式进行维护管理。比如下图,我们在网络上传递的数据,要求加密,但是加密后
密文比较大,需要压缩后再传输,而且按照业务要求,需要检查报文中携带的用户信息是否
合法,于是我们实现了 5 个 Handler: 解压(入)Handler、压缩(出)handler、解密(入)
Handler、加密(出) Handler、授权(入) Handler

        如果一个消息或者任何其他的入站事件被读取 那么它会从 ChannelPipeline 的头部开
始流动,但是只被处理入站事件的 Handler 处理,也就是解压(入)Handler、解密(入)Handler、 授权(入) Handler,最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。
        数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下, 数据将从
链的尾端开始流动,但是只被处理出站事件的 Handler 处理,也就是加密(出) Handler、
压缩(出)handler,直到它到达链的头部为止。 在这之后,出站数据将会到达网络传输层,
也就是我们的 Socket
        Netty 能区分入站事件的 Handler 和出站事件的 Handler ,并确保数据只会在具有相同定
向类型的两个 ChannelHandler 之间传递。

        所以在我们编写 Netty 应用程序时要注意,分属出站和入站不同的 Handler 在业务没
特殊要求的情况下 是无所谓顺序的,正如我们下面的图所示 比如‘压缩(出)handler‘可
以放在‘解压(入)handler‘和‘解密(入) Handler‘中间,也可以放在‘解密(入) Handler
‘和‘授权(入) Handler‘之间。
        而同属一个方向的 Handler 则是有顺序的,因为上一个 Handler 处理的结果往往是下一
Handler 的要求的输入。比如入站处理,对于收到的数据,只有先解压才能得到密文,才
能解密,只有解密后才能拿到明文中的用户信息进行授权检查,所以解压 -> 解密 -> 授权这个
三个入站 Handler 的顺序就不能乱。

ChannelPipeline 上的方法

        既然 ChannelPipeline 以双向链表的形式进行维护管理 Handler ,自然也提供了对应的方
法在 ChannelPipeline 中增加或者删除、替换 Handler
        addFirst、 addBefore addAfter addLast
        将一个 ChannelHandler 添加到 ChannelPipeline
        remove 将一个 ChannelHandler ChannelPipeline 中移除
        replace ChannelPipeline 中的一个 ChannelHandler 替换为另一个 ChannelHandler
        get 通过类型或者名称返回 ChannelHandler
        context 返回和 ChannelHandler 绑定的 ChannelHandlerContext
        names 返回 ChannelPipeline 中所有 ChannelHandler 的名称
        ChannelPipeline 的 API 公开了用于调用入站和出站操作的附加方法。

ChannelHandlerContext

        ChannelHandlerContext 代表了 ChannelHandler ChannelPipeline 之间的关联,每当有
ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext ,为什么需
要这个 ChannelHandlerContext ?前面我们已经说过, ChannelPipeline 以双向链表的形式进
行维护管理 Handler ,毫无疑问, Handler 在放入 ChannelPipeline 的时候必须要有两个指针
pre next 来说明它的前一个元素和后一个元素,但是 Handler 本身来维护这两个指针合适
吗?想想我们在使用 JDK LinkedList 的时候,我们放入 LinkedList 的数据是不会带这两个指
针的, LinkedList 内部会用类 Node 对我们的数据进行包装,而类 Node 则带有两个指针 pre
next
        所以,ChannelHandlerContext 的主要作用就和 LinkedList 内部的类 Node 类似。
不过 ChannelHandlerContext 不仅仅只是个包装类,它还提供了很多的方法,比如让事
件从当前 ChannelHandler 传递给链中的下一个 ChannelHandler ,还可以被用于获取底层的
Channel ,还可以用于写出站数据。

ChannelChannelPipeline ChannelHandlerContext 上的事件传播

        ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel
Channel-Pipeline 本身上, 但是有一点重要的不同。 如果调用 Channel 或者 ChannelPipeline
的这些方法,它们将沿着整个 ChannelPipeline 进行传播。而调用位于 ChannelHandlerContext
上的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该
ChannelPipeline 中的下一个(入站下一个,出站上一个)能够处理该事件的 ChannelHandler

        我们用一个实际例子来说明,比如服务器收到对端发过来的报文,解压后需要进行解密,
结果解密失败,要给对端一个应答。
        如果发现解密失败原因是服务器和对端的加密算法不一致,应答报文只能以明文的压缩
格式发送,就可以在解密 handler 中直接使用 ctx.write 给对端应答,这样应答报文就只经过
压缩 Handler 就发往了对端;
        其他情况下,应答报文要以加密和压缩格式发送,就可以在解密 handler 中使用
channel.write() 或者 channelpipeline.write() 给对端应答,这样应答报文就会流经整个出站处理
过程。

ChannelHandlerContext API

alloc 返回和这个实例相关联的 Channel 所配置的 ByteBufAllocator
bind 绑定到给定的 SocketAddress ,并返回 ChannelFuture
channel 返回绑定到这个实例的 Channel
close 关闭 Channel ,并返回 ChannelFuture
connect 连接给定的 SocketAddress ,并返回 ChannelFuture
deregister 从之前分配的 EventExecutor 注销,并返回 ChannelFuture
disconnect 从远程节点断开,并返回 ChannelFuture
executor 返回调度事件的 EventExecutor
fireChannelActive 触发对下一个 ChannelInboundHandler 上的 channelActive() 方法(已
连接)的调用
fireChannelInactive 触发对下一个 ChannelInboundHandler 上的 channelInactive() 方法
(已关闭)的调用
fireChannelRead 触发对下一个 ChannelInboundHandler 上的 channelRead() 方法(已接
收的消息)的调用
fireChannelReadComplete 触发对下一个 ChannelInboundHandler 上的
channelReadComplete() 方法的调用
fireChannelRegistered 触发对下一个 ChannelInboundHandler 上的
fireChannelRegistered() 方法的调用
fireChannelUnregistered 触发对下一个 ChannelInboundHandler 上的
fireChannelUnregistered() 方法的调用
fireChannelWritabilityChanged 触发对下一个 ChannelInboundHandler 上的
fireChannelWritabilityChanged() 方法的调用
fireExceptionCaught 触发对下一个 ChannelInboundHandler 上的
fireExceptionCaught(Throwable) 方法的调用
fireUserEventTriggered 触发对下一个 ChannelInboundHandler 上的
fireUserEventTriggered(Object evt) 方法的调用
handler 返回绑定到这个实例的 ChannelHandler
isRemoved 如果所关联的 ChannelHandler 已经被从 ChannelPipeline 中移除则返回 true
name 返回这个实例的唯一名称
pipeline 返回这个实例所关联的 ChannelPipeline
read 将数据从 Channel 读取到第一个入站缓冲区;如果读取成功则触发一个
channelRead 事件,并(在最后一个消息被读取完成后)通知 ChannelInboundHandler
channelReadComplete(ctx) 方法
write 通过这个实例写入消息并经过 ChannelPipeline
writeAndFlush 通过这个实例写入并冲刷消息并经过 ChannelPipeline
当使用 ChannelHandlerContext API 的时候,有以下两点:
⚫ ChannelHandlerContext 和 ChannelHandler 之间的关联(绑定)是永远不会改变的,
所以缓存对它的引用是安全的;
⚫ 相对于其他类的同名方法,ChannelHandlerContext 的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。

 ChannelHandler

ChannelHandler 接口

        从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler ,它充当了所有
处理入站和出站数据的应用程序逻辑的容器。 ChannelHandler 的方法是由网络事件触发的。
事实上, ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另
外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。
        举例来说,ChannelInboundHandler 是一个你将会经常实现的子接口。这种类型的
ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。
当你要给连接的客户端发送响应时,也可以从 ChannelInboundHandler 直接冲刷数据然后输
出到对端。应用程序的业务逻辑通常实现在一个或者多个 ChannelInboundHandler 中。
        这种类型的 ChannelHandler 接收入站事件和数据,这些数据随后将会被应用程序的业
务逻辑所处理。
        Netty 定义了下面两个重要的 ChannelHandler 子接口:
                ChannelInboundHandler——处理入站数据以及各种状态变化;
                ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。

ChannelInboundHandler 接口

        下面列出了接口 ChannelInboundHandler 的生命周期方法。这些方法将会在数据被接收
时或者与其对应的 Channel 状态发生改变时被调用。正如我们前面所提到的,这些方法和
Channel 的生命周期密切相关。
        channelRegistered Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
        channelUnregistered Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调
        channelActive Channel 处于活动状态时被调用; Channel 已经连接 / 绑定并且已经就
        channelInactive Channel 离开活动状态并且不再连接它的远程节点时被调用
        channelReadComplete Channel 上的一个读操作完成时被调用
        channelRead 当从 Channel 读取数据时被调用
        ChannelWritabilityChanged
        当 Channel 的可写状态发生改变时被调用。可以通过调用 Channel isWritable() 方法
来检测 Channel 的可写性。与可写性相关的阈值可以通过
Channel.config().setWriteHighWaterMark() Channel.config().setWriteLowWaterMark() 方法来
设置
        userEventTriggered ChannelnboundHandler.fireUserEventTriggered() 方法被调用时被
调用。
注意: channelReadComplete channelRead 这两个方法非常让人搞不清两者的区别是
什么,我们先放下这个疑问,后面会有解释。

ChannelOutboundHandler 接口

        出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel Channel
        Pipeline 以及 ChannelHandlerContext 调用。
        所有由 ChannelOutboundHandler 本身所定义的方法:
        bind(ChannelHandlerContext,SocketAddress,ChannelPromise)
        当请求将 Channel 绑定到本地地址时被调用
        connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise)
        当请求将 Channel 连接到远程节点时被调用
        disconnect(ChannelHandlerContext,ChannelPromise)
        当请求将 Channel 从远程节点断开时被调用
        close(ChannelHandlerContext,ChannelPromise) 当请求关闭 Channel 时被调用
        deregister(ChannelHandlerContext,ChannelPromise)
        当请求将 Channel 从它的 EventLoop 注销时被调用
        read(ChannelHandlerContext) 当请求从 Channel 读取更多的数据时被调用
        flush(ChannelHandlerContext) 当请求通过 Channel 将入队数据冲刷到远程节点时被调
        write(ChannelHandlerContext,Object,ChannelPromise) 当请求通过 Channel 将数据写到
远程节点时被调用

ChannelHandler 的适配器

        有一些适配器类可以将编写自定义的 ChannelHandler 所需要的工作降到最低限度,因
为它们提供了定义在对应接口中的所有方法的默认实现。因为你有时会忽略那些不感兴趣的
事件,所以 Netty 提供了抽象基类 ChannelInboundHandlerAdapter (处理入站) 和
ChannelOutboundHandlerAdapter (处理出站)。
        我们可以使用 ChannelInboundHandlerAdapter ChannelOutboundHandlerAdapter 类作
为自己的 ChannelHandler 的起始点。这两个适配器分别提供了 ChannelInboundHandler
ChannelOutboundHandler 的基本实现。通过扩展抽象类 ChannelHandlerAdapter ,它们获得
了它们共同的超接口 ChannelHandler 的方法。
        不过 ChannelOutboundHandler 有个非常让人迷惑的 read 方法, ChannelOutboundHandler
不是处理出站事件的吗?怎么会有 read 方法呢?其实这个 read 方法不是表示读数据,而是
表示业务发出了读( read )数据的要求,这个要求也会封装为一个事件进行传播,这个事件
因为是业务发出到网络的,自然就是个出站事件,而且这个事件触发的就是
ChannelOutboundHandler read 方法。
        如果我们的 Handler 既要处理入站又要处理出站怎么办呢?这个时候就可以使用类
ChannelDuplexHandler ,当然也可以同时实现 ChannelOutboundHandler,
ChannelInboundHandler 这两个接口,自然就要麻烦很多了。

Handler 的共享和并发安全性

        ChannelHandlerAdapter 还提供了实用方法 isSharable() 。如果其对应的实现被标注为
Sharable ,那么这个方法将返回 true ,表示它可以被添加到多个 ChannelPipeline
        这就牵涉到了我们实现的 Handler 的共享性和线程安全性。回顾我们的 Netty 代码,在
pipeline 安装 Handler 的时候,我们基本上是 new Handler 的实例
        因为每个 socketChannel 有自己的 pipeline 而且每个 socketChannel 又是和线程绑定的,
所以这些 Handler 的实例之间完全独立的,只要 Handler 的实例之间不是共享了全局变量,
Handler 的实例是线程安全的。
        但是如果业务需要我们在多个 socketChannel 之间共享一个 Handler 的实例怎么办呢?
比如统计服务器接受到和发出的业务报文总数,我们就需要用一个 Handler 的实例来横跨所
有的 socketChannel 来统计所有 socketChannel 业务报文数。
        为了实现这一点,我们可以实现一个 MessageCountHandler ,并且在
MessageCountHandler 上使用 Netty @Sharable 注解,然后在安装 MessageCountHandler
例到 pipeline 时,共用一个即可。当然,因为 MessageCountHandler 实例是共享的,所以在
实现 MessageCountHandler 的统计功能时,请务必注意线程安全,我们在具体实现时就使用
Java 并发编程里的 Atomic 类来保证这一点。
        具体代码请参考包 cn.tuling.nettybasic.sharehndler

资源管理和 SimpleChannelInboundHandler

        回想一下我们在 NIO 中是如何接收和发送网络数据的?都是首先创建了一个 Buffer ,应
用程序中的业务部分和 Channel 之间通过 Buffer 进行数据的交换:
        Netty 在处理网络数据时,同样也需要 Buffer ,在 Read 网络数据时由 Netty 创建 Buffer
Write 网络数据时 Buffer 往往是由业务方创建的。不管是读和写, Buffer 用完后都必须进行
释放,否则可能会造成内存泄露。
        在 Write 网络数据时,可以确保数据被写往网络了, Netty 会自动进行 Buffer 的释放,
但是如果 Write 网络数据时,我们有 outBoundHandler 处理了 write() 操作并丢弃了数据,没
有继续往下写,要由我们负责释放这个 Buffer ,就必须调用 ReferenceCountUtil.release 方法,
否则就可能会造成内存泄露。
        在 Read 网络数据时,如果我们可以确保每个 InboundHandler 都把数据往后传递了,也
就是调用了相关的 fireChannelRead 方法, Netty 也会帮我们释放,同样的,如果我们有
InboundHandler 处理了数据,又不继续往后传递,又不调用负责释放的 ReferenceCountUtil.release 方法,就可能会造成内存泄露。
        但是由于消费入站数据是一项常规任务,所以 Netty 提供了一个特殊的被 称为 SimpleChannelInboundHandler 的 ChannelInboundHandler 实现。这个实现会在数
据被 channelRead0() 方法消费之后自动释放数据。
        同时系统为我们提供的各种预定义 Handler 实现,都实现了数据的正确处理,所以我们
自行在编写业务 Handler 时,也需要注意这一点: 要么继续传递,要么自行释放

内置通信传输模式

        NIO io.netty.channel.socket.nio 使用 java.nio.channels 包作为基础 —— 基于选择器的方
        Epoll io.netty.channel.epoll JNI 驱动的 epoll() 和非阻塞 IO 。这个传输支持只有在
Linux 上可用的多种特性,如 SO_REUSEPORT ,比 NIO 传输更快,而且是完全非阻塞的。将
NioEventLoopGroup 替换为 EpollEventLoopGroup , 并且将 NioServerSocketChannel.class
换为 EpollServerSocketChannel.class 即可。
        OIO io.netty.channel.socket.oio 使用 java.net 包作为基础 —— 使用阻塞流
        Local io.netty.channel.local 可以在 VM 内部通过管道进行通信的本地传输
        Embedded io.netty.channel.embedded Embedded 传输,允许使用 ChannelHandler 而又
不需要一个真正的基于网络的传输。在测试 ChannelHandler 实现时非常有用
        

引导 Bootstrap

        网络编程里,“服务器”和“客户端”实际上表示了不同的网络行为;换句话说,是监
听传入的连接还是建立到一个或者多个进程的连接。
        因此,有两种类型的引导: 一种用于客户端(简单地称为 Bootstrap),而另一种
(ServerBootstrap)用于服务器。 无论你的应用程序使用哪种协议或者处理哪种类型的数据,
唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器。
        比较 Bootstrap
         ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而 Bootstrap 则是由
想要连接到远程节点的客户端应用程序所使用的。
        第二个区别可能更加明显 。引导一个客户端只需要一个 EventLoopGroup,但是一个
ServerBootstrap 则需要两个(也可以是同一个实例)。
         因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务器
自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理
传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。
        与 ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建
Channel EventLoop 。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel 分配
一个 EventLoop

ChannelInitializer

        Netty 提供了一个特殊的 ChannelInboundHandlerAdapter 子类: public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
        它定义了下面的方法:
         protect ed abstract void initChannel(C ch) throws Exception;
        这个方法提供了一种将多个 ChannelHandler 添加到一个 ChannelPipeline 中的简便方法。
你只需要简单地向 Bootstrap 或 ServerBootstrap 的实例提供你的 ChannelInitializer 实现即
可,并且一旦 Channel 被注册到了它的 EventLoop 之后,就会调用你的 initChannel()版本。
在该方法返回之后,ChannelInitializer 的实例将会从 ChannelPipeline 中移除它自己。
        所以,在我们自己的应用程序中,如果存在着某个 handler 只使用一次的情况,也可以
仿造 ChannelInitializer ,用完以后将自己从 ChannelPipeline 中移除自己,比如授权 handler
某客户端第一次连接登录以后,进行授权检查,检查通过后就可以把这个授权 handler 移除
了。如果客户端关闭连接下线,下次再连接的时候,就是一个新的连接,授权 handler 依然
会被安装到 ChannelPipeline ,依然会进行授权检查。

ChannelOption

ChannelOption 的各种属性在套接字选项中都有对应。

ChannelOption.SO_BACKLOG

        ChannelOption.SO_BACKLOG 对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,服务端
处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来
的时候,服务端将不能处理的客户端连接请求放在队列中等待处理。所以操作系统里一般有
两个队列,一个是 ACCEPT 队列,保存着已经完成了 TCP 的三次握手的连接,一个 SYN 队列,
服务器正在等待 TCP 的三次握手完成的队列。
        BSD派生系统里 backlog 指的就是 SYN 队列的大小,在 Linux 的实现里 backlog 相对来说,
就含糊不清了,有些内核版本指的是 ACCEPT 队列 +SYN 队列合起来的大小,有的是指 SYN
队列的大小。
        但是从 Linux 2.2 开始, backlog 的参数行为在 Linux 2.2 中发生了变化,现在它指定等
待接受的完全建立的套接字的队列长度,而不是不完整的连接请求的数量。 不完整套接字
队列的最大长度可以使用 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置,默认值为 128
        如果 backlog 参数大于 /proc/sys/net/core/somaxconn 中的值,那么它会被静默截断为
128 。在 2.4.25 之前的内核中,此限制是硬编码值,后续内核版本也可以通过 vim
/etc/sysctl.conf 来修改,包括我们前面所说的 tcp_max_syn_backlog 也可以在此处修改,然后
通过命令 sysctl -p 生效。

ChannelOption.SO_REUSEADDR

        ChanneOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR ,这个参数表示允许重复使用本地地址和端口,
        比如,多网卡(IP )绑定相同端口,比如某个进程非正常退出,该程序占用的端口可能
要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能
够释放此端口,不设置 SO_REUSEADDR 就无法正常使用该端口。
但是注意,这个参数无法做到让应用绑定完全相同 IP + Port 来重复启动。

ChannelOption.SO_KEEPALIVE

        Channeloption.SO_KEEPALIVE 参数对应于套接字选项中的 SO_KEEPALIVE ,该参数用于设 置 TCP 连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数
据交流的连接。当设置该选项以后,如果在两小时内没有数据的通信时, TCP 会自动发送一
个活动探测数据报文。

ChannelOption.SO_SNDBUF ChannelOption.SO_RCVBUF

        ChannelOption.SO_SNDBUF 参数对应于套接字选项中的 SO_SNDBUF
ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF 这两个参数用于操作接
收缓冲区和发送缓冲区的大小,接收缓冲区用于保存网络协议站内收到的数据,直到应用程
序读取成功,发送缓冲区用于保存发送数据,直到发送成功。

ChannelOption.SO_LINGER

        ChannelOption.SO_LINGER 参数对应于套接字选项中的 SO_LINGER,Linux 内核默认的处理
方式是当用户调用 close ()方法的时候,函数返回,在可能的情况下,尽量发送数据,不
一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close() 的调
用时间,直到数据完全发送

ChannelOption.TCP_NODELAY

        ChannelOption.TCP_NODELAY 参数对应于套接字选项中的 TCP_NODELAY, 该参数的使用
Nagle 算法有关, Nagle 算法是将小的数据包组装为更大的帧然后进行发送,而不是输入
一次发送一次 , 因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发
送,虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使
Nagle 算法,使用于小数据即时传输,于 TCP_NODELAY 相对应的是 TCP_CORK ,该选项是
需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。

相关文章:

Netty-Netty组件了解

EventLoop 和 EventLoopGroup 回想一下我们在 NIO 中是如何处理我们关心的事件的&#xff1f;在一个 while 循环中 select 出事 件&#xff0c;然后依次处理每种事件。我们可以把它称为事件循环&#xff0c;这就是 EventLoop 。 interface io.netty.channel. EventLoo…...

银行的压力测试如何进行?

为什么要进行压力风险测试&#xff1f; 压力风险测试的最终目的是测试银行在极度恶劣的市场环境中是否有足够的资本维持运转。 题主链接中的一级资本充足率(Tier 1 capital ratio) 亦即衡量标准&#xff0c;这个数字越大&#xff0c;表明银行资本约充裕&#xff0c;可以在停止…...

QtService、托盘程序使用

1、QtService 使用QtService实现Qt后台服务程序 用QT创建一个Windows Service以及踩到的若干坑 2、托盘程序 Qt之程序最小化托盘显示及操作 Qt系统托盘程序的实现...

使用Linux防火墙管理HTTP流量

在Linux系统中&#xff0c;防火墙是用于控制网络流量的重要工具。通过防火墙&#xff0c;你可以根据需要限制、过滤或允许特定的网络流量&#xff0c;从而提高系统的安全性。在处理HTTP流量时&#xff0c;防火墙可以帮助你实施访问控制、流量监控和其他安全策略。 iptables i…...

图鸟引入多套字体图标的方式教程

https://www.yuque.com/tuniao/qunyou/tgfvpg ①上传icon&#xff0c;生成iconfont.css 将css文件放这里 app.vue全局引入 适当改造iconfont.css的写法&#xff0c;方便调用...

在openEuler环境下快速编译GreatSQL RPM包

在上一篇中&#xff0c;已经介绍了在CentOS环境下编译GreatSQL RPM包的过程&#xff0c;本文再介绍如何在openEuler环境下编译GreatSQL RPM包。 运行环境是docker中的openEuler 22.03 x86_64&#xff1a; $ docker -v Docker version 20.10.10, build b485636$ docker run -itd…...

C语言基础语法跟练 day3

31、不使用累计乘法的基础上&#xff0c;通过移位运算&#xff08;<<&#xff09;实现2的n次方的计算。 #include <stdio.h> int main() {int i 0;scanf("%d",&i);printf("%d",1<<i);return 0; } 32、问题&#xff1a;一年约有 3.…...

【控制篇 / 策略】(7.4) ❀ 01. IP地理位置数据库和地理地址对象 ❀ FortiGate 防火墙

【简介】在很多使用环境下&#xff0c;我们需要对指定国家的IP地址进行允许或禁止访问操作&#xff0c;例如只允许访问国内IP。以前只能手动添加IP地址对象到地址组&#xff0c;繁杂且效率低下&#xff0c;Fortinet提供了基于地理位置的IP库&#xff0c;就可以解决这个问题。 I…...

NX二次开发点通过云配准获取相同体

先找到体的参考方向&#xff08;这个参考方向对于相同体重合之后是相同的&#xff09;&#xff0c;这个时候我们的思路是三个不共线的点确定一个坐标系&#xff0c;然后和绝对方向求转换矩阵。然后获取体的所有边的几何中心&#xff0c;把这些点通过转换矩阵转换之后存起来&…...

5.4 Android BCC环境搭建(eadb版 下)

四,BCC使用示例 这里以tcplife为例,来显示TCP会话的生命周期和吞吐量统计。 4.1 进入/bcc/tools目录 root@localhost:/bcc# cd tools/ root@localhost:/bcc/tools# ls CMakeLists.txt javacalls.sh rubystat_example.txt argdist.py javacalls_e…...

【AI视野·今日Robot 机器人论文速览 第七十四期】Wed, 10 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Wed, 10 Jan 2024 Totally 17 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Hold em and Fold em: Towards Human-scale, Feedback-Controlled Soft Origami Robots Authors Immanuel Ampomah Mensah, Je…...

服务端性能测试——性能测试工具JMeter-L1

第一遍没学懂&#xff0c;后续文章会更新~ 目录&#xff1a; 1.JMeter介绍与安装Meter简介JMeter安装2.JMeter的运行JMeter运行、界面功能简介3.使用代理服务器录制请求录制压测脚本&#xff08;一&#xff09;Web端脚本录制方法4.测试计划5.线程组6.控制器7.JMeter采样器/取…...

C# OpenCvSharp DNN FreeYOLO 目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN FreeYOLO 目标检测 效果 模型信息 Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Float[1, 3, 192, 320] --------------------------------------------------------------- Outp…...

U盘启动安装win11遇到缺少计算机所需的介质驱动程序问题

一、使用U盘制作启动盘遇到问题 下载了windows原版镜像&#xff0c;验证了md5&#xff0c;确保文件没有损坏。使用ultroiso制作u盘启动盘&#xff0c;开始安装后出现下图的报错&#xff1a; 在网上搜索解决方案&#xff0c;主要有以下几种&#xff1a; 安装的时候&#xff0c…...

正则表达式、文件访问(Python实现)

一、主要目的&#xff1a; 1.了解正则表达式的基本概念和处理过程。 2.掌握使用正则表达式模块 Re 进行字符串处理的方法。 3.了解文件的基本概念和类型。 4.掌握在 Python 中访问文本文件的方法和步骤。 5.熟悉在 Python 中访问二进制文件的方法和步骤。 二、主要内容和结…...

ES高级查询

ES中提供了一种强大的检索数据方式&#xff0c;这种检索方式称为Query DSL&#xff0c;这种方式的丰富查询语法让ES检索变得更强大&#xff0c;更简洁。 1.常见查询 1.1查询所有[match_all] match_all关键字&#xff1a;返回索引中的全部文档。 GET /products/_search { &…...

RT-Thread入门笔记6-空闲线程及两个常用的钩子函数

空闲线程 空闲线程是一个比较特殊的系统线程&#xff0c;它具备最低的优先级。当系统中无其他就绪线程可运行时&#xff0c;调度器将调度到空闲线程。 空闲线程还负责一些系统资源回收以及将一些处于关闭态的线程从线程调度列表中移除的动作 空闲线程在形式上是一个无线循环结…...

网络正常运行时间监控工具

正常运行时间是衡量系统可靠性的指标&#xff0c;表示为机器工作和可用时间的百分比。当提到 IT 网络时&#xff0c;正常运行时间是衡量网络设备、网站和其他服务的可用性的指标。网络正常运行时间通常以百分位数来衡量&#xff0c;例如“五个 9”&#xff0c;这意味着系统在 9…...

DEJA_VU3D - Cesium功能集 之 112-获取圆节点(1)

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码…...

Matlab 建文件夹保存本次仿真图表数据和参数

文章目录 前言代码 前言 有时候跑的仿真参数非常多&#xff0c;保存结果的时候需要把仿真参数和数据一起保存&#xff0c;为方便起见&#xff0c;查了一下怎么建文件夹自动保存本次仿真图表数据和参数&#xff0c;再也不用担心忘记结果是什么参数跑出来的了~ 代码 % 定义变量…...

@JsonFormat与@DateTimeFormat

JsonFormat注解很好的解决了后端传给前端的格式&#xff0c;我们通过使用 JsonFormat可以很好的解决&#xff1a;后台到前台时间格式保持一致的问题 其次&#xff0c;另一个问题是&#xff0c;我们在使用WEB服务的时&#xff0c;可 能会需要用到&#xff0c;传入时间给后台&am…...

半监督学习 - 自训练(Self-training)

什么是机器学习 半监督学习中的自训练&#xff08;Self-training&#xff09;是一种利用已标记数据和未标记数据进行模型训练的方法。以下是自训练的详细教程&#xff1a; 步骤一&#xff1a;准备数据集 标记数据集&#xff1a; 收集和标记一小部分数据&#xff0c;用于有监…...

outlook邮件群发单显技巧?群发怎么单显?

outlook邮件群发单显如何设置&#xff1f;QQ邮箱怎么群发单显&#xff1f; 在群发邮件时&#xff0c;如何让每个收件人只看到自己的名字&#xff0c;而不是其他人的名字&#xff0c;这就涉及到所谓的“单显”技巧。下面蜂邮EDM就为大家揭秘Outlook邮件群发单显的奥秘。 outlo…...

【REST2SQL】07 GO 操作 Mysql 数据库

【REST2SQL】01RDB关系型数据库REST初设计 【REST2SQL】02 GO连接Oracle数据库 【REST2SQL】03 GO读取JSON文件 【REST2SQL】04 REST2SQL第一版Oracle版实现 【REST2SQL】05 GO 操作 达梦 数据库 【REST2SQL】06 GO 跨包接口重构代码 MySQL是一个关系型数据库管理系统&#xf…...

[UI5] ODATA V4中的CRUD

文章目录 前言一、Read二、Create三、Update四、Delete 前言 ODATA V4在CRUD方面与V2截然不同。 这篇文章简单介绍V4中是如何进行CRUD操作 一、Read Model不再有read方法&#xff0c; 一般是把Path绑定到View中进行读取&#xff0c; 如果需要额外的读取数据&#xff0c;可使用…...

js封装根据年月日获取星座效果demo(整理)

//根据年月日获取星座 function getZodiacSign(dateString) {// 用法:const dateStr 2024-01-11;// const zodiacSign getZodiacSign(dateStr);const date new Date(dateString);const month date.getMonth() 1;const day date.getDate();if ((month 1 && day &…...

Vue.js设计与实现阅读-2

Vue.js设计与实现阅读-2 1、前言2、框架设计的核心要素2、1 提升用户体验2、2 控制代码体积2、3 Tree-Shaking2、4 特性开关2、5 错误处理 1、前言 上一篇我们了解到了 命令式和声明式的区别&#xff0c;前者关注过程&#xff0c;后者关注结果了解了虚拟dom存在的意义&#x…...

GEM5 McPAT教程:源代码解读McPAT NoC功耗 arbiter部分

简介 McPAT用的很多,大多只是写个python或perl脚本替换xml文件.没有深入到为什么xml脚本这些值要换,以及这写填进去xml的值是怎么影响计算的.本问从源代码一步步读下来,解释每一步是如何计算的. power 构成: 动态功耗其实更相关于energy McPAT的power 核心是两类,动态和静态…...

使用组合框QComboBox模拟购物车

1.组合框: QComboBox 组合框&#xff1a;QComboBox 用于存放一些列表项 实例化 //实例化QComboBox* comboBox new QComboBox(this);1.1 代码实现 1.1.1 组合框的基本函数 QComboBox dialog.cpp #include "dialog.h" #include "ui_dialog.h"Dialog::Dialog…...

云服务器十大服务商——云服务器哪家好用

云服务器哪家便宜&#xff1f;2024最新整理你要的都在这&#xff01;头部云厂商阿里云、腾讯云、华为云、京东云、UCloud等都在降价&#xff0c;阿腾云atengyun.com分享2024年云服务器租用价格给你惊喜&#xff01; 便宜云服务器阿里云腾讯云华为云 2024年便宜云服务器汇总&…...

php做的网站手机能看到/平台推广精准客源

传送门 今天在HackerRank上翻到一道高精度题&#xff0c;于是乎就写了个高精度的模板&#xff0c;说是模板其实就只有乘法而已。 Extra long factorials Authored by vatsalchanana on Jun 16 2015Problem Statement You are given an integer N. Print the factorial of this …...

威海建设集团信息网站/湖北seo诊断

操作系统的主要功能之一就是文件的管理。文件管理是由文件系统来实现的&#xff0c;Linux 系统支持多种格式 的文件系统&#xff0c;本届我们主要讲解下文件系统的类型&#xff0c;以及文件操作的一些常用命令。 2.8.1 Linux 文件系统类型 我们在使用 Windows 系统的时候&…...

用python做 网站论坛/整站优化包年

finalize 它是 Object 中的一个方法&#xff0c;如果子类重写它&#xff0c;垃圾回收时此方法会被调用&#xff0c;可以在其中进行资源释放和清理工作 将资源释放和清理放在 finalize 方法中非常不好&#xff0c;非常影响性能&#xff0c;严重时甚至会引起 OOM&#xff0c;从 …...

重庆电商网站建设/seo资源网站排名

★★★ 本文源自AI Studio社区精品项目&#xff0c;【点击此处】查看更多精品内容 >>> 基于深度学习的心电信号分析 一、项目背景 近年来&#xff0c;随着人工智能和算法的发展&#xff0c;以机器学习和深度学习为主的研究技术被 广泛的应用于医疗领域研究中&#x…...

wordpress 用户密码的加密算法/搜索引擎优化英文简称

第1章 C及其开发环境概述一、问答题1&#xff0e;结构化程序设计主要有哪些劣势&#xff1f;【答案】(1)程序的开发效率低下(2)程序的可重用性差(3)程序的维护成本高(4)无法适应信息的快速膨胀和多样化2&#xff0e;面向对象的程序设计有哪些优点&#xff1f;【答案】(1)程序的…...

怎么做简单的微信浏览的网站/成都最新数据消息

http://www.52c51.com/article/63.html市场上盛行的电子高压灭蚊手拍&#xff08;简称“电蚊拍”&#xff09;&#xff0c;以其实用、灭蚊效果好、无化学污染、安全卫生等优点&#xff0c;普遍受到人们的欢迎。其实这种电灭蚊拍线路简单&#xff0c;具有一定电子制作能力的青少…...