当前位置: 首页 > 网络应用技术

一篇文章谈论了管道(上图)

时间:2023-03-08 12:40:23 网络应用技术

  本系列Netty源代码分析文章基于4.1.56。最终版本

  在上一系列文章中,作者对每个人进行了详细的详细详细分析反应堆模型的创建。

  例如,在反应堆的启动过程中,您需要创建Nioserversocketchannel。在创建过程中,为NioServersocketchannel创建了一条管道,用于OP_ACCEPT事件的布置。

  当NioServersocketchannel成功注册到主反应堆时,将在管道中触发ChannelRegistery事件的传播。

  当NioServersocketchannel绑定端口成功时,通道活动事件的传输将在管道中触发。

  例如,在反应堆接收连接期间,当客户端启动连接并完成三个握手时,相应的插座将存储在内核中的完整连接队列中。然后,JDK选择器此时将通知主反应堆。事件活跃。最后,主反应堆开始执行NioServersocketchannel NioMessageunsafe#读取方法的基础操作类,以在Nioserversocketchannel中的管道中传播频道Read事件。

  将Niosocketchannel注册到子反应器,然后在Niosocketchannel的管道中传播ChannelRegistery事件,最后传播通道活动。

  在文章“如何有效接收网络数据”中,我们还提到,当子反应堆从Niosocketchannel中的客户端读取客户端的请求数据时,我们将在NioSocketchannel的管道中传播ChannelRead事件。ChannelReadComplete事件将传播。

  在文章“一个文本以了解发送数据的整个过程”的文章中,我们分别通过写入方法和冲洗方法来谈论在niosocketchannel管道中传输写入事件和冲洗事件的过程。

  我带每个人都在上一系列文章中回顾了管道的使用场景,但是在这些系列文章中,与管道相关的详细信息有关的详细信息没有在完整而全面的描述中描述。事件的沟通场景。

  Netty将为每个通道分配一个独立的管道,并通过创建通道创建管道。

  我早些时候介绍了Netty Server期间创建了Nioserversocketchannel。

  无论是在Nioserversocketchannel中的管道还是Niosocketchannel中的管道,他们最终都将其委托给他们的父级抽象计算机。

  在上一系列文章中,作者多次提到管道的结构是由由ChannelHandlerContext类型组成的节点组成的两条链接列表。头节点是headScontext,端节点是tailContext。如下:

  我们知道,两条路链接列表结构的管道中的节点元素是ChannelHandlerContext。由于HeadContext用作管道的头节点,因此它必须是ChannelHandLercontext类型,因此它需要继承ApproctionChanelHandlerContext.ADD ChannelHandler进行管道,以便您使用HeadContext来修复First ChannelHandlerContext。

  在“ 1. ChannelHandlerContext”的文章中,“一个文本以了解Netty发送数据的整个过程”,作者详细介绍了ChannelHandlerContext在管道中的作用。忘记的学生可以回头。

  同时,HeadContext实现了ChannelInboundHandler和ChannelOutBoundHandler接口,表明HeadContext是ChannelHandLercontext和ChannelHandler。它可以同时处理入站事件和出站事件。

  我们还注意到,与该频道相对应的不安全的基础操作类在HeadContext中举行,这也表明,管道中IO事件的传播最终将落在最终IO处理的HeadContext中。这是入站的起点。事件和出站事件的终点。这里还可以看到,除了Sentons外,HeadContext还进行了与基础渠道相关的操作。

  例如,我们在“反应堆实现”中引入的NioServersocketchannel将在注册完成后,将在注册完成后依次传输。

  Nioserversocketchannel成功地与端口结合后,将触发通道活动。它将按顺序从HeadContext向后传输,并通过headContext中的unsafe.beginread()注册op_accept事件。

  同样,在NioSocketchannel成功地向子反应器注册后。ChannelRegisteed事件和ChannelActive事件将从HeadContext触发,以向后扩展在Pipeline.une.under.under。并在HeadContext中注册OP_READ事件,以将OP_READ事件注册到子反应器。

  在“一篇了解Netty发送数据的整个过程”中写入事件和冲洗事件最终将从背部到HeadContext的管道中传播到HeadContext。在HeadContext的相应事件回调函数中,不安全的类频道发送数据以发送数据。

  从本节的引入中,我们可以看到Netty中频道相关基础操作的基础操作调用是在HeadContext中触发的。

  同样,作为两道链接列表结构的管道,TailContext也需要继承AbstractChanlHandlerContext。但它还实现了ChannelInboundHandler。

  这表明,除了ChannelHandlerContext之外,TailContext也是ChannelInlerContext。

  2.2.1 TailContext作为ChannelHandlerContext TailContext作为ChannelHandLercontext的角色负责从管道结束到HeadContext。

  在这里,我们可以看到,当我们在“自定义ChannelHandler”中调用时,我们将在AbstractCtChannel中触发Pipeline.write.write(msg),最后在DefaultChannelPipeline中调用Tail.write(MSG)。以及其他出站事件传播的原因。

  并且我们的自定义ChannelHandler被封装在ChannelHandlerContext中,以将其添加到管道中,并且用于加载自定义ChannelHandler的ChannelHandLercontext与TailContext相同。

  本质

  这是当前Echoserverhandler的写入事件,以在管道中向前扩展直至HeadContext。

  2.2.2 TailContext作为ChannelInboundHandler的角色。最后,TailContext作为ChannelInboundHandler的作用是为Pipeline的入站事件的传播做一个口袋。

  这里提到的底部治疗是什么意思?

  例如,我们之前介绍的内容是由niosocketchannel触发的通道注册事件,该事件在sub反应器和ChannelActive事件中注册。或阅读反应堆线程中NioSocketchannel中的请求数据触发的通道Read事件和ChannelReadComplete事件。

  这些入站事件将首先从管道中的HeadContext向后传递。

  极端情况是,如果未通过事件处理管道中的所有ChannelInboundHandler,并继续向后传播。如下示例代码所示:

  最后,这些入站事件没有在管道中处理,最后扩散到tailcontext。

  在TailContext中,必须进行无法处理的入站事件。例如,丢弃MSG并释放DirectByteBuffer被占用以避免内存泄漏。

  在上一系列文章中,作者多次介绍了Netty中的IO事件分为两类:入站事件和出站事件。实际上,如果严格划分,应将其分为三类。是异常异常事件类型。

  在事件传播方面,异常事件与入站事件相同。它一直从管道的HeadContext或从当前的ChannelHandler到TailContext。

  根据事件类型的分类,负责处理事件的ChannelHandler也将分为两类:

  那么,我们经常说什么活动和出站活动?

  Netty将由其支持的所有异步事件代表。它是在ChannelHandLermask类中定义的。Netty Framework可以轻松地知道用户自定义(ChannelInboundHandler或ChannelOutBoundBoundHandler)自定义用户的哪种类型的ChannelHandler。

  此外,有太多的入站事件,用户对所有入站事件都不感兴趣。用户可以在自定义的ChannelInboundHandler中介绍他们有趣的入站事件恢复,以实现对特定入站事件的监视。

  这些用户感兴趣的入站事件集合也将以与ChannelHandler相对应的ChannelHandlerContext中的掩码形式保存。因此,当特定的入站事件开始在管道中传播时,可以根据保存在相应的channelhandlercontext中保存的入站事件的入站事件来收集Netty。要在用户定义的ChannelHandler或对入站事件不感兴趣的ChannelHandler中执行相应的回调方法。

  从上面的描述中,我们也可以窥视。Netty介绍了ChannelHandlerContext封装以封装ChannelHandler的原因。在代码设计方面,它遵循单一责任的原则。ChannelHandler是用户最常见的NetTy组件。在核心IO处理上,用户只需要关心哪些异步事件感兴趣并考虑相应的处理逻辑。他们不需要关心如何在管道中传输异步事件,如何选择具有执行条件的频道手机以进行执行或跳过。这些面条逻辑,Netty全部包装为ChannelHandlerContext的上下文信息中的上下文信息。Netty框架本身已处理。

  在上述内容中,作者还将介绍事件相关部分的详细信息。之所以引入此处是为了让每个人都感到使用面具进行收集操作的便利性。当提到的一系列文章很多次时,当频道向反应堆注册IO事件时,Netty还存储了对频道感兴趣的IO事件以selectionkey中的int兴趣的蒙版形式。

  接下来,我将介绍这些入站事件,并整理这些入站事件的触发时机。每个人都可以根据自己的业务需求进行灵活的监控。

  在传播本节中介绍的这些入站事件的过程中,如果在执行相应的事件回调函数期间发生异常,则将触发相应的ChannelHandler中的异常事件。

  当然,用户可以在异常事件中选择是否执行CTX.FireExceptionCatught(原因),以决定是否继续向后传播异常事件。

  当Netty内核处理连接和接收连接以及数据读取异常时,它将触发整个管道中异常事件的传播。

  作者为什么要强调异常发生在入站事件的传播期间,然后将调整例外情况?

  因为入站事件通常是由NetTy内核传输的,并且出站事件通常由用户触发,例如写入事件或处理业务逻辑后用户触发的潮汐事件。

  用户触发出站事件后,通常会获得频道提升。用户可以将各种侦听器添加到channelpromise中。当出站事件在点差期间发生异常,Netty将通知用户此channelpromise,但不会触发excpedionCaump的回调。

  例如,我们不会在写入事件的传输过程中触发异常事件恢复。

  外站事件中冲洗事件的传播是一个例外。当冲洗事件在管道的传播过程中发生异常时,它将触发相应异常的频道频道Handler的异常事件。因为齐平方法的签名不会返回到用户。

  当主反应堆启动时,将创建并初始化NioServersocketchannel,然后在主反应器中注册。当注册成功时,频道注册事件将在Nioserversocketchannel的管道中传播。

  当主反应堆接收客户端启动的连接时,将创建并初始化NioSocketchannel,然后用子反应器注册。当注册成功时,将传输Niosocketchannel中的管道活动。

  注意:此时,相应的通道尚未将IO事件注册到相应的反应器。

  在NioServersocketchannel成功注册并触发ChannelRegistery事件的传播之后,将在管道中触发绑定事件,并且绑定事件是一个出站事件,该事件将从管道中从终点传播。执行真实的绑定操作。

  当Netty Server Nioserversocketchannel弯曲端口成功时,这是一个真正的活动,然后在管道中传输了ChannelActive事件。

  在我们还提到判断Nioserversocketchannel是否活跃的标准之前:底部JDK Nio Serversocketchannel已打开,并且serversocket已完成。

  客户端NioSocketchannel触发了频道活动事件相对简单。当Niosocketchannel成功注册到子反应器并触发ChannelRegistery时,通道活动事件将在管道中传输。

  客户端NioSocketchannel的徽标是:是否打开了基础JDK Nio Socketchannel并连接了底层。毫无疑问,必须连接此处的插座。因此,直接触发了通道活动。

  注意:此时,频道将转到相应的反应堆进行注册IO事件。当用户定义的ChannelHandler接收到ChannelActive事件时,它表明IO事件已注册到反应器中。

  当客户端有一个新的连接请求时,服务器上NioServersocketchannel上的OP_ACCEPT事件将处于活动状态。然后,主反应堆将在读取循环中不断调用serversocketchannel.acccept()。16次。

  在NioServersocketchannel中,可以在管道中触发一个新连接。每个接受都可以在完整的读取循环结束后,将触发ChannelReadComplete事件。

  当客户端NioSocketchannel请求数据时,NioSocketchannel上的OP_READ事件处于活动状态,然后子反应堆还将在读取loop中读取NioSocketchannel中的请求数据。

  在读取循环的读取过程中,每个读取都会触发管道中的通道读事件。完整的读取循环结束后,ChannelReadComplete事件将在管道中触发。

  应该注意的是,当触发ChannelReadComplete事件时,并不意味着Niosocketchannel中的请求数据已被读取。可能的情况是要发送的请求数据太多。读取循环已经达到了最大限制。次数为16次,并且在阅读后没有全部退出读取循环。一开始读取循环已退出,将触发ChannelReadComplete事件。有关详细信息,您可以查看。作者的文章“如何有效接收网络数据”。

  在处理业务逻辑以获取业务处理的结果之后,CTX.Write(MSG)触发了Pipeline中写入事件的传输。

  最后,Netty将将数据MSG写入NioSocketchannel的channeloutboundbuffer。并等待用户从数据msg调用齐平操作,以从channeloutboundbuffer发送数据msg,并将其写入底部的后续缓冲区中层。

  当末端的接收过程非常慢或网络条件极度拥挤时,TCP滑动窗口会不断减小,这导致发送端的发送速度。(MSG),这将导致ChannelOutBoundBuffer急剧增加,这可能导致OOM。Netty引入了高水线和低水线线,以控制Channeloutboundbuffer的记忆占用。

  当Channeoutboundbuffer中的内存职业超过高水位线时,Netty将将相应的通道放在不成文状态,并在管道中触发ChannelWritabilityChaanged事件。

  当ChannelOutBoundBuffer中的内存职业低于低水位位置时,Netty将将相应的Niosocketchannel设置为写作状态,并再次触发ChannelWritabilityChaned事件。

  用户可以确定当前通道是否可以通过CTX.Channel()编写。ctx.channel()中的issrable()。iSsritable()。iSsritable()。

  Netty提供了一种事件扩展机制,该机制使用户可以自定义异步事件,从而可以灵活地定义各种复杂场景的处理机制。

  让我们看一下如何自定义Netty中的异步事件。

  随着我们的源代码解释的加深,我们还将看到Netty本身也定义了许多USEREVENT事件。我们还将稍后介绍。您可以在这里了解相关用法。

  关闭通道后,它将触发管道中的通道活动事件的传播,然后触发Channelunregistery事件的传播。

  我们可以响应在入站类型中的ChannelInactive和Channelunregister中的通道词和通道注册事件。

  建立连接后事件的序列恰恰相反。建立连接后,首先触发ChannelRegistery事件,然后触发通道活动事件。

  像入站事件一样,出站事件也具有相应的掩码表示。LET查看出站事件的触发时机:

  每个人都需要注意读取事件和频道读事件之间的区别。

  我们已经在ChannelRead事件之前介绍了。当Nioserversocketchannel接收新连接时,它将在管道上触发ChannelRead事件。

  当在NioSocketchannel上有一个请求数据时,在读取Read Loop中读取请求数据时,ChannelRead事件将在其管道上传输。

  读取事件与ChannelRead事件完全不同。读取事件专门指通道感知IO事件的能力。op_accept事件对应于Nioserversocketchannel的感知能力,Niosocketchannel对应于OP_READ事件的感知。

  读取事件的触发因素是在频道需要注册类型事件事件(例如op_accept Event和op_read事件)时触发触发器。对通道感兴趣的事件。

  例如,NioServersocketchannel对OP_ACCEPT事件感兴趣,而NioSocketchannel对OP_READ事件感兴趣。

  当我们较早地介绍通道活动事件时,我们提到,当频道处于活动状态时,频道活动将在管道中扩散。HeadContext中的频道活动将触发回调中读取事件的传播。

  在HeadContext的读取事件回调中,将调用Channel的基础操作类不安全的BeginRead方法,该方法将在此方法中注册对频道感兴趣的IO事件。对于NioServerSocketchannel,OP_ACCEPT事件在此处注册。对于NioSocketchannel,OP_READ事件在此处注册。

  小心的学生可能会注意到与包含自动座位属性的频道相对应的配置类,那么自动启动到底是什么?

  实际上,这是Netty提供的一种防止OOM的背压机制。想象一下,当发送数据非常大并且发送速度非常快时,服务器处理速度非常慢,并且消耗暂时无法使用。一对数据的结束是不断发送数据。服务端的反应堆线程必须在读取循环中读取并分配字节缩写以读取数据。服务器的业务线程无法再次处理,这导致大量数据没有时间来处理占用A大量的记忆空间,导致OOM。

  面对这种情况,我们可以将AutoRead属性设置为False。然后Netty将取消反应堆中香奈儿(Chanel)中读取感兴趣的阅读事件的类型。从那时起,反应堆将不再聆听相应的事件。然后,通道不会读取数据。

  这里NioServersocketchannel对应于OP_ACCEPT事件,NioSocketchannel对应于OP_READ事件。

  当服务器的处理速度返回正常时,我们可以将AutoRead属性设置为true。这样,Netty将在管道中触发读取事件,并最终注册读取的读取类型事件,对频道感兴趣的频道感兴趣的频道对反应堆感兴趣HeadContext中读取事件恢复方法中的不安全#beginReam方法。

  读取事件可以理解为读取频道的能力。当阅读能力时,ChanneRead可以读取特定数据。

  写入事件和冲洗事件已在文章中引入“一条文本,以了解发送数据的完整过程”。

  在用户完成业务请求后,在业务线程中主动触发写作和冲洗事件。

  用户可以由ChannelHandlerContext触发,也可以由频道触发。

  不同之处在于,如果它是通过ChannelHandlerContext触发的,则写入事件或冲洗事件将从管道中的当前频道Handler传播到HeadContext。

  如果它是由频道触发的,则写入事件和冲洗事件将从管道的尾部节点尾部传播到HeadContext。

  当然,还有一个WriteAndflush方法,该方法还将分为ChannelHandlerContext触发器和频道触发器。触发WriteAndflush后,写入事件将首先在管道中扩散,最后,Flush事件在管道中扩散。

  写入事件的网络处理将最终将发送数据写入相应的写作缓冲列表ChannelOutBoundbuffer。这次,数据没有发送,而是在写作缓冲区队列中被缓存。这也是Netty的核心设计,以实现异步写作。

  最终,要发送的数据是从通道中的写作缓冲区队列通过冲洗操作获得的,并且要发送的数据写入插座的发送缓冲区中。

  当用户调用ChannelHandler中的以下方法时,该通道将由关闭事件触发,以从管道中的后背传播。

  我们可以回应出站的ChannelHandler中的密切事件。

  最后,关闭事件将在管道中向前扩展,直到head node headConnect并在HeadContext中完成连接闭合操作。当连接关闭时,将在管道中触发通道词的事件和通道注册事件。

  用户可以调用以下代码以从反应堆出售当前频道。

  我们可以在出站的频道Handler中回应deregister事件。

  最后,删除事件将扩散到管道中的头节点,而HeadContext中的基础频道取消注册操作将在HeadContext中完成。反应堆的频道下注,反应堆将不会在频道上收听IO事件。并在管道中触发通道注册事件。

  在Netty客户端中,我们可以使用NioSocketchannel的连接方法来触发管道中的连接事件。

  我们可以在出站的ChannelHandler中响应连接事件。

  最后,连接事件将建立一个请求,以触发管道HeadContext的Head节点中的基础连接。当客户端成功连接到服务器时,ChannelActive事件将在客户端NioSocketchannel的管道中传播。

  在Netty客户端中,我们还可以调用NioSocketchannel的断开方法以触发管道中的断开事件,这将导致Niosocketchannel关闭。

  我们可以在出站的ChannelHandler中响应断开事件。

  最后,断开事件将扩散到HeadContext,并在HeadContext完成了基础中断操作。当客户成功关闭客户时,ChannelInactive事件和Channelunregistery将在管道中触发。

  本文是Netty中相关内容的设计和实施的上半年。在上部,我们在Netty中介绍了管道的结构和IO事件的分类。并详细介绍了这些异步事件的触发时间和传播路径。

  在下半部,我将介绍管道的详细设计和实现~~

  原始:https://juejin.cn/post/7098276158210310180