Netty 是一个非阻塞(异步)、事件驱动的网络框架,用多线程处理 IO 事件。
Netty 服务端与客户端都是由 Bootstrap 引导程序开始的,对于服务端,引导类是 ServerBootstrap,对于客户端,引导类是 Bootstrap。
从 ServerBootstrap 开始,Netty Server 的结构如下:
一个典型的 Netty Server 如下:
public class NettyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
}
});
serverBootstrap.bind(8000);
}
}
ServerBootstrap 有两个 EventLoopGroup:
EventLoopGroup 是一个事件循环集合,每个 EventLoopGroup 都有一个或多个 EventLoop。EventLoop 是一个事件循环线程,它通过 Java NIO 的 selector 管理多个 Channel。
Channel 接受 socket 传来的数据,进入到每个 Channel 都有的 ChannelPipeline 中,使用责任链模式,数据经过经过一系列处理器 ChannelHandler 逻辑处理,将数据传出。 ChannelPipeline 是 ChannelHandler 的一个链表,如果一个入站 (inbound) 事件触发,会从第一个 ChannelHandler 开始遍历所有的 ChannelHandler;如果出站 (outbound) 事件触发,则会从最后一个 ChannelHandler 开始反向遍历执行所有逻辑。 每个 ChannelHandler 添加到 ChannelPipeline 之后,都会创建一个 ChannelHandlerContext,并与 ChannelHandler 关联绑定,每个 ChannelHandlerContext 都有 prev, next 指针,与其他的 ChannelHandlerContext 关联,这样就形成了 ChannelPipeline 的链式结构。
Netty 处理事件 IO 有很多线程,处理时尽量不要阻塞线程,因为阻塞会降低程序的性能。在添加 ChannelHandler 到 ChannelPipeline 时,可以指定一个 EventExecutorGroup,可以从中获取 EventExecutor。
在 ChannelPipeline 添加 ChannelHandler 时,可以为 ChannelHandler 指定 EventExecutor,用线程的方式执行 ChannelHandler 的方法。
在 Java NIO 中也有 ByteBuffer 的缓冲类,但它读写索引是放在一起的,而且更改读写状态需要使用 filp() 方法转换状态,整体使用比较麻烦。 Netty 对 Java NIO 进行了优化,形成优化后的数据容器 ByteBuf。它针对 ByteBuffer 类的缺点进行了优化,分为了读写两部分,可以在任意位置读取数据,开发者只需要调整数据索引位置,以及再次开始读操作即可。所以 ByteBuf 本质就是一个由不同的索引分别控制读访问和写访问的字节数组。ByteBuf 的数据结构如下所示:
容器里面的的数据分为三个部分:
ByteBuf 有三种模式:
Netty 是一个典型的 反应器设计模式 (Reactor)。Reactor 模式是一种基于事件响应的模式,将多个客户进行统一的分离和调度,同步、有序的处理请求。
注:在 Netty 中采用了主从线程模型的 Reactor,即 Bootstrap 的两个 NioEventLoopGroup:bossGroup, workerGroup。
Reactor 有三种模式:
尽管我们在应用层面使用了 Netty,但是对于操作系统来说,只认 TCP 协议。尽管我们的应用层是按照 ByteBuf 为 单位来发送数据,但是到了底层操作系统仍然是按照字节流发送数据,因此,数据到了服务端,也是按照字节流的方式读入,然后到了 Netty 应用层面,重新拼装成 ByteBuf。 而这里的 ByteBuf 与客户端按顺序发送的 ByteBuf 可能是不对等的。因此,我们需要在客户端根据自定义协议来组装我们应用层的数据包,然后在服务端根据我们的应用层的协议来组装数据包,这个过程通常在服务端称为拆包,而在客户端称为粘包。
Netty 自带的拆包器:
注:此外还有不怎么常用的行拆包器和分隔符拆包器;