Channel、EventLoop 和 ChannelFuture
这一节将对 Channel、EventLoop 和 ChannelFuture 类进行讨论,它们组合在一起,可以被认为是 Netty 网络抽象的代表:
- Channel —— Socket
- EventLoop —— 控制流、多线程处理、并发
- CHannelFuture —— 异步通知
1. Channel 接口
Netty 的 Channel 接口对应 Java 网络编程的 Socket,大大降低了直接使用 Socket 类的复杂性。此外,Channel 也拥有其他预定义的实现类:
- EmbeddedChannel:测试 ChannelHandler
- LocalServerChannel:用于同一个 JVM 内部实现 client 和 server 之间的通信
- NioSocketChannel:异步的客户端 TCP Socket 连接
- NioServerSocketChannel:异步的服务器端 TCP Socket 连接
- NioDatagramChannel:异步的 UDP 连接
- NioSctpChannel:异步的客户端 Sctp 连接
- NioSctpServerChannel:异步的 Sctp 服务器端连接
- OioSocketChannel:同步的客户端 TCP Socket 连接
- OioServerSocketChannel:同步的服务器端 TCP Socket 连接
- OioDatagramChannel:同步的 UDP 连接
- OioSctpChannel:同步的 Sctp 服务器端连接
- OioSctpServerChannel:同步的客户端 TCP Socket 连接
2. EventLoop 接口
EventLoop 用于处理连接的生命周期中所发生的事件,下图说明了 Channel、EventLoop、Thread 以及 EventLoopGroup 之间的关系
这些关系是:
- 一个 EventLoopGroup 包含一个或多个 EventLoop
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定
- 所有由 EventLoop 处理的 IO 事件都将在它专有的 Thread 上被处理
- 一个 Channel 在它的生命周期内只注册一个 EventLoop
- 一个 EventLoop 可能会被分配到一个或多个 Channel
3. ChannelFuture 接口
Netty 所有的 IO 操作都是异步的,一个操作可能不会立即返回结果,因此我们需要一种用于在之后的某个时间点确定其结果的方法。Netty 提供了 ChannelFuture 接口,其 addListener() 方法注册一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功0得到通知)
ChannelHandler 和 ChannelPipeline
1. ChannelHandler 接口
ChannelHandler 可以看作是负责处理入站和出站数据的应用程序逻辑的容器,例如将数据从一个格式转换为另一种格式,处理抛出的异常等等。ChannelInboundHandler 是一个经常使用的子接口,这种类型的 ChannelHandler 接收入站事件和数据,这些数据随后将被你的业务逻辑锁处理。当你想要给客户端发送响应时,也可以从 ChannelInboundHandler 冲刷数据,通常应用程序的业务逻辑通常驻留在一个或者多个 ChannelInboundHandler 中
2. ChannelPipeline 接口
ChannelPipeline 为 ChannelHandler 链提供了容器,并定义了用于在该链上传播入站和出站事件流的 API。当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline
ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
- 一个 ChannelInitializer 的实现被注册到了 ServerBootstrap 中
- 当 ChannelInitializer.initChannel() 方法被调用时,ChannelInitializer 将在 ChannelPipeline 中安装一组自定义的 ChannelHandler
- ChannelInitializer 将它自己从 ChannelPipeline 中移除
ChannelHandler 可以看作是处理往来 ChannelPipeline 事件(包括数据)的任何代码的通用容器,使事件流经过 ChannelPipeline 是 ChannelHandler 的工作,在应用程序的初始化或者引导阶段被安装。这些 ChannelHandler 接收事件、执行所实现的业务逻辑,并将数据传递给链中的下一个 ChannelHandler。它们的执行顺序由它们被添加的顺序所决定。实际上,ChannelPipeline 就是这些 ChannelHandler 的编排顺序
当 ChannelHandler 被添加到 ChannelPipeline 时,它会被分配一个 ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定,虽然这个对象可以被用于获取底层的 Channel,但它还是主要用于写出站数据
在 Netty 中有两种发送消息的方式,可以直接写到 Channel 中,也可以写到和 ChannelHandler 相关联的 ChannelHandlerContext 对象中。前一种方式将会导致消息从 ChannelPipeline 的尾端开始流动,后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动
编码器和解码器
当你通过 Netty 发送或者接收一个消息时,就会发生一次数据转换。入站消息会被解码,即从字节转换成另一种格式,通常是一个 Java 对象。如果是出站消息,则会发生相反方向的转换,从当前格式被编码为字节。为此,Netty 为编码器和解码器提供了不同类型的抽象类,这些基类的名称将类似于 ByteToMessageDecoder 或 MessageToByteEncoder。对于一些特殊类型,可能还会有 ProtobufEncoder 和 ProtobufDecoder 这样的名称,用来支持 Google 的 Protocol Buffers
使用 Netty 提供的编码器/解码器,你会发现对于入站数据来说,channelRead 方法/事件已经被重写。对于每个从入站 Channel 读取的消息,将调用重写后的 channelRead 方法。随后,它将调用解码器提供的 decode() 方法,将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。出站消息是反过来的,编码器将消息转换为字节,并将它们转发给下一个 ChannelOutboundHandler
引导
Netty 的引导类为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定的端口,或者将一个进程连接到另一个运行在某个指定主机的指定端口上的进程。通常我们把前面的用例称为引导一个服务器,后面的用例称为引导一个客户端。因此,有两种类型的引导:一种用于客户端(Bootstrap),而另一种(ServerBootstrap)用于服务器
两种类型的引导类区别如下:
-
ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而 Bootstrap 则是由想要连接到远程节点的客户端应用程序使用
-
引导一个客户端只需要一个 EventLoopGroup,但是一个 ServerBootstrap 则需要两个。因为服务器需要两组不同的 Channel,第一组只包含一个 ServerChannel,代表服务器自身已绑定到某个本地端口的正在监听的套接字,而第二组将包含所有已创建的用来处理传入客户端连接的 Channel
与 ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建 Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel 分配一个 EventLoop