网络编程与Netty(三) 初识Netty-(Ⅲ)

ChannelPipeline 和 ChannelHandlerContext
ChannelPipeline 接口

​ 当Channel被创建时,它将会自动的分配一个新的ChannelPipeline,并且是不可替换的。Channel不能附加到另一个ChannelPipeline。这个分配工作是由netty完成的,不需要开发人员操作。

​ 当数据在ChannelHandler中被处理,然后传递下到下一个ChannelHandler,实际上就是在Pipeline中传递,而传递的顺序由它们被添加的顺序所决定的。

ChannelHandler 的生命周期

​ 在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调用下面这些方法。

  • handlerAdded(ChannelHandlerContext ctx)当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
  • handlerRemoved(ChannelHandlerContext ctx)当从 ChannelPipeline 中移除 ChannelHandler 时被调用
  • exceptionCaught(ChannelHandlerContext ctx)当处理过程中在 ChannelPipeline 中有错误产生时被调用
ChannelPipeline中ChannelHandler

​ 前面也有讲到过ChannelHandler可以分为入站和出站两种,而每个Handler都与一个ChannelPipeline进行绑定,所以我们会把入站和出站两种ChannelHandler加入到同一个Pipeline中。如果一个消息或者其他入站类型的事件被读取,那么它会从ChannelPipeline的头部开始流动,流经每个ChannelInboundHandler,或被处理(如果不往后传则需要手动释放内存)或被往后传,最终,数据将会到达ChannelPipeline的尾端,此时才算处理都结束了。出栈的数据处理大致也是一样的,数据会从ChannelOutboundHandler链的尾端开始流动,流经每个ChannelOutboundHandler或被处理或被往后传,最终到达链条头部。到达头部后,数据将会到达网络传输到对端,也就是我们所说Socket。

​ InboundHandler和OutboundHandler都混合添加到同一个Pipeline,那他们的顺序会如何,有哪些是需要注意的呢。虽然入站和出站两种Handler在同一个Pipeline中,但是我们完全可以拆开来看待,如下图

在这里插入图片描述

​ 将原本在同一个Pipeline中的所有Handler按入站和出站分类,只要入站处理器之间的相对位置保持不变,出站处理器之间的相对位置保持不变,则混合在一起的位置没有要求,Netty能区分ChannelInboundHandler的实现和ChanneloutboundHandler实现,确保数据准确的在出站或者入站的同类型处理器中被处理和传递。

ChannelPipeline上的方法
  • addFirstaddBeforeaddAfteraddLast 将一个 ChannelHandler 添加到 ChannelPipeline 中

  • remove 将一个 ChannelHandler 从 ChannelPipeline 中移除

  • replace 将 ChannelPipeline 中的一个 ChannelHandler 替换为另一个 ChannelHandler

  • get 通过类型或者名称返回 ChannelHandler

  • context 返回和 ChannelHandler 绑定的 ChannelHandlerContext

  • names 返回 ChannelPipeline 中所有 ChannelHandler 的名称

    还有一些关于关于调用入站或者出站操作的附加方法,如:fireChannelActive,fireChannelRead等

ChannelHandlerContext

​ 我们可以发现在很多方法中都用ChannelHandlerContext(后续简称为ctx)作为参数传入,这个对象可以被用于获取相关联的pipeline和channel,但是我们一般还是主要用于写出站数据的操作。

​ 前面说到了ChannelHandler会被加入到ChannelPipelin中,而且是双向链表的形式,那么就意味着我们需要在ChannelHandler中至少加入前后两个节点的引用,但ChannelHandler主要是用于处理事件的,加入引用后就破坏了单一职责,所以每次将ChannelHandler加入到Pipeline时,都会创建ChannelHandlerContext,将ChannelHandler包装成ctx,ctx的主要功能就是管理整个Pipeline中ChannelHandler的关系。

​ ChannelHandlerContext有很多的方法,仔细对比它与Channel和Pipeline中的方法,可以发现有很多是相同的,但是他们的作用却有一点重要的不同,如果调用Channel和Pipeline上的这些方法,它将会从头向尾(入站事件)或从尾向头(出站事件)开始传播,流经整个Pipeline传递。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline中的下一个(入站向后,出站向前)对应类型的ChannelHandler。

​ 下面主要简单介绍下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

在大部分情况下,我们通常调用ctx中的方法,因为其流经的ChannelHandler链路更短,尽可能的提高性能。

内置通信传输模式
  • 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)用于服务器。唯一决定它使用哪种引导的是作为一个客户端还是作为一个服务器,而不取决于应用程序使用哪种协议或哪种类型的数据等因素。

BootstrapServerBootStrap
网络编程中的作用连接到远程主机和端口绑定到一个本地端口
EventLoopGroup12

​ 第一个区别,ServerBootStrap将绑定到一个端口,因为服务器必须要监听连接,而Bootstrap则是由想要连接远程节点的客户端应用程序所使用的。

​ 第二个区别就是引导一个客户端只需要一个EventLoopGroup,但是一个ServerBootStrap则需要两个(之前的代码我们传了一个进去也是可以是因为在构造函数的地方帮我们用了两次同一个实例)。因为服务器需要两组不同的Channel。第一组将只包含一个ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受连接都有一个)的Channel。

在这里插入图片描述

​ 与ServerChannel相关联的EventLoopGroup将分配一个负责为传入连接请求创建Channel的EventLoop。一旦连接被接受,第二个EventLoopGroup就会给它的Channel分配一个EventLoop。

​ 在引导过程中添加多个ChannelHandler,Netty提供了一个特殊的ChannelInboundHandlerAdapter子类:

public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter{
	protected abstract void initChannel(C ch) throws Exception;
}

​ 它定义了一个抽象方法initChannel方法,这个方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。只需要简单的向Bootstrap或ServerBootstrap的实例提供你的 ChannelInitializer 实现即可,并且一旦 Channel 被注册到了它的 EventLoop 之后,就会调用你的 initChannel()方法。在该方法返回之后,ChannelInitializer 的实例将会从 ChannelPipeline 中移除它自己。

ChannelOption

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

  1. ChannelOption.SO_BACKLOG

    ​ ChannelOption.SO_BACKLOG 对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小

  2. ChannelOption.SO_REUSEADDR

    ​ ChanneOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口。

    ​ 比如,某个服务器进程占用了 TCP 的 80 端口进行监听,此时再次监听该端口就会返回错误,使用该参数就可以解决问题,该参数允许共用该端口,这个在服务器程序中比较常使用,比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置 SO_REUSEADDR就无法正常使用该端口。

  3. ChannelOption.SO_KEEPALIVE

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

  4. ChannelOption.SO_SNDBUF 和 ChannelOption.SO_RCVBUF

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

  5. ChannelOption.SO_LINGER

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

  6. ChannelOption.TCP_NODELAY

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值