分布式理论、架构设计学习:Netty

本文介绍了Netty,一个由JBOSS提供的高性能、易用的网络应用框架,它简化了NIO的开发过程。Netty因其高吞吐量、低延迟、资源效率和对各种传输协议的支持而广泛应用于互联网、大数据和游戏行业。文章详细讨论了Netty的线程模型,包括单线程、线程池和Netty特有的BossGroup和WorkerGroup模型,并解析了Netty的核心组件如ChannelHandler、ChannelPipeline、ChannelHandlerContext、ChannelFuture和EventLoopGroup。最后,文章通过一个简单的案例展示了如何使用Netty实现客户端和服务器的通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Netty认识

Netty 是由 JBOSS 提供一个异步的、 基于事件驱动的网络编程框架

Netty 可以帮助你快速、 简单的开发出一 个网络应用, 相当于简化和流程化了 NIO 的开发过程。 作为当前最流行的 NIO 框架, Netty 在互联网领域、 大数据分布式计算领域、 游戏行业、 通信行业等获得了广泛的应用, 知名的 Elasticsearch 、 Dubbo 框架内部都采用了 Netty。

1.1 为什么使用Netty

NIO缺点

  • NIO 的类库和 API 繁杂,使用麻烦。你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等.
  • 可靠性不强,开发工作量和难度都非常大
  • NIO 的 Bug。例如 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%

Netty优点

  • 对各种传输协议提供统一的 API
  • 高度可定制的线程模型 —— 单线程、一个或多个线程池
  • 更好的吞吐量,更低的等待延迟
  • 更少的资源消耗
  • 最小化不必要的内存拷贝

2. 线程模型

2.1 单线程模型

2.2 线程池模型

2.3 Netty 模型


Netty 抽象出两组线程池, BossGroup 专门负责接收客 户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理任务的线程, 每个 NioEventLoop 都有一个 selector, 用于监听绑定在其上的 socket 网络通道。 NioEventLoop 内部采用串行化设计, 从消息的读取 > 解码 > 处理 > 编码 > 发送, 始终由 IO 线 程 NioEventLoop 负责。

3. Netty核心组件

3.1 ChannelHandler 及其实现类

ChannelHandler 接口定义了许多事件处理的方法, 我们可以通过重写这些方法去实现具体的业务逻辑

public void channelActive(ChannelHandlerContext ctx) // 通道就绪事件
public void channelRead(ChannelHandlerContext ctx, Object msg) // 通道读取数据事件 
public void channelReadComplete(ChannelHandlerContext ctx) // 数据读取完毕事件
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) //通道发生异常事件

3.2 ChannelPipeline

ChannelPipeline 是一个 Handler 的集合, 它负责处理和拦截 inbound 或者 outbound 的事件和操作, 相当于一个贯穿 Netty 的链。

ChannelPipeline addFirst(ChannelHandler... handlers) // 把一个业务处理类(handler) 添加到链中的第一 个位置
ChannelPipeline addLast(ChannelHandler... handlers) // 把一个业务处理类(handler) 添加到链中的最后 一个位置

3.3 ChannelHandlerContext

这是事件处理器上下文对象,Pipeline链中的实际处理节点。每个处理节点ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用。
常用方法如下所示

ChannelFuture close() // 关闭通道
ChannelOutboundInvoker flush() // 刷新
ChannelFuture writeAndFlush(Object msg)//将数据写到 ChannelPipeline 中,当前ChannelHandler的下一个 ChannelHandler 开始处理(出站)

3.4 ChannelFuture

表示 Channel 中异步 I/O 操作的结果, 在 Netty 中所有的 I/O 操作都是异步的, I/O 的调用会直接返回, 调用者并不能立刻获得结果, 但是可以通过 ChannelFuture 来获取 I/O 操作 的处理状态。
常用方法如下所示:

Channel channel() // 返回当前正在进行 IO 操作的通道 
ChannelFuture sync() // 等待异步操作执行完毕

3.5 EventLoopGroup 和其实现类 NioEventLoopGroup

EventLoopGroup 是一组 EventLoop 的抽象, Netty 为了更好的利用多核 CPU 资源, 一般会有多个 EventLoop 同时工作, 每个 EventLoop 维护着一个 Selector 实例。 EventLoopGroup 提供 next 接口, 可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。 在 Netty 服务器端编程中, 我们一般都需要提供两个EventLoopGroup, 例如: BossEventLoopGroup 和 WorkerEventLoopGroup

public NioEventLoopGroup() // 构造方法 
public Future<?> shutdownGracefully() // 断开连接, 关闭线程

3.6 ServerBootstrap 和 Bootstrap

ServerBootstrap 是 Netty 中的服务器端启动助手,通过它可以完成服务器端的各种配置; Bootstrap 是 Netty 中的客户端启动助手, 通过它可以完成客户端的各种配置。
常用方法如下 所示:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) // 该方法用于 服务器端,用来设置两个EventLoop 
public B group(EventLoopGroup group) // 该方法用于客户端,用来设置一个 EventLoop
public B channel(Class<? extends C> channelClass) // 该方法用来设置一个服务器端的通道实现
public <T> B option(ChannelOption<T> option, T value) // 用来给 ServerChannel 添加配置
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) // 用来给接收到的通道添加配置 
public ServerBootstrap childHandler(ChannelHandler childHandler) // 该方法用来设置业务处理类(自定义的handler)
public ChannelFuture bind(int inetPort) // 该方法用于服务器端, 用来设置占用的端口号
public ChannelFuture connect(String inetHost, int inetPort) // 该方法用于客户端, 用来连接服务器端

4. Netty版案例实现

需求:使用 netty 客户端给服务端发送数据,服务端接收消息打印

  • 引入依赖
<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>4.1.52.Final</version>
</dependency>
  • 服务端
package com.study.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

// 接收客户端请求,打印在控制台
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        //1.创建2个线程池对象
        //bossGroup 负责接收用户连接
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //workGroup 负责处理用户的io读写操作
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        //2.创建启动引导类
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        //3.设置启动引导类
        //添加到组中,两个线程池,第一个位置的线程池就负责接收,第二个参数就负责读写
        serverBootstrap.group(bossGroup,workGroup)
                //给我们当前设置一个通道类型
                .channel(NioServerSocketChannel.class)
                //绑定一个初始化监听
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    //事件监听Channel通道
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        //获取pipeLine
                        ChannelPipeline pipeline = nioSocketChannel.pipeline();
                        //绑定编码
                        pipeline.addFirst(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        //绑定我们的业务逻辑
                        pipeline.addLast(new SimpleChannelInboundHandler<String>() {
                            protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
                                //获取入栈信息,打印客户端传递的数据
                                System.out.println(msg);
                            }
                        });
                    }
                });

        //4.启动引导类绑定端口
        ChannelFuture future = serverBootstrap.bind(9999).sync();

        //5.关闭通道
        future.channel().closeFuture().sync();
    }
}

在编写 Netty 程序时,一开始都会生成 NioEventLoopGroup 的两个实例,分别是 bossGroup 和 workerGroup,也可以称为 parentGroup 和 childGroup,为什么创建这两个实例,作用是什么?

  1. 可以这么理解,bossGroup 和workerGroup 是两个线程池, 它们默认线程数为 CPU 核心数乘以 2,bossGroup 用于接收客户端传过来的请求,接收到请求后将后续操作交由 workerGroup 处理。

  2. 接下来我们生成了一个服务启动辅助类的实例 bootstrap,boostrap 用来为 Netty 程序的启动组装配置一些必须要组件,例如上面的创建的两个线程组。

  3. channel 方法用于指定服务器端监听套接字通道NioServerSocketChannel,其内部管理了一个 Java NIO 中的ServerSocketChannel实例。

  4. channelHandler 方法用于设置业务职责链,责任链是我们下面要编写的,责任链具体是什么,它其实就是由一个个的 ChannelHandler 串联而成,形成的链式结构。正是这一个个的 ChannelHandler 帮我们完成了要处理的事情。

  5. ChannelInitializer 继承 ChannelInboundHandlerAdapter,用于初始化 Channel 的 ChannelPipeline。通过initChannel 方法参数 sc 得到 ChannelPipeline 的一个实例。

  6. 当一个新的连接被接受时, 一个新的 Channel 将被创建,同时它会被自动地分配到它专属的 ChannelPipeline通过 addLast 方法将一个一个的 ChannelHandler 添加到责任链上并给它们取个名称(不取也可以,Netty 会给它个默认名称),这样就形成了链式结构。在请求进来或者响应出去时都会经过链上这些 ChannelHandler 的处理。

  7. 最后再向链上加入我们自定义的 ChannelHandler 组件,处理自定义的业务逻辑

  8. 接着我们调用了 bootstrap 的 bind 方法将服务绑定到 8080 端口上,bind 方法内部会执行端口绑定等一系列操,使得前面的配置都各就各位各司其职,sync 方法用于阻塞当前 Thread,一直到端口绑定操作完成。最后是应用程序将会阻塞等待直到服务器的 Channel 关闭。

  • 客户端
package com.study.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;

//客户端给服务器发送数据
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        //1.创建连接池对象
        NioEventLoopGroup group = new NioEventLoopGroup();

        //2.创建客户端的启动引导类 BootStrap
        Bootstrap bootstrap = new Bootstrap();

        //3.配置启动引导类
        bootstrap.group(group)
                //设置通道为Nio
                .channel(NioSocketChannel.class)
                //设置Channel初始化监听
                .handler(new ChannelInitializer<Channel>() {
                    //当前该方法监听channel是否初始化
                    protected void initChannel(Channel channel) throws Exception {
                        //设置编码
                        channel.pipeline().addLast(new StringEncoder());
                    }
                });

        //4.使用启动引导类连接服务器 , 获取一个channel
        Channel channel = bootstrap.connect("127.0.0.1", 9999).channel();

        //5.循环写数据给服务器
        while (true) {
            //给服务器写数据
            channel.writeAndFlush("hello server .. this is client ...");
            Thread.sleep(2000);
        }
    }
}

使用Netty之后,一方面Netty对NIO封装得如此完美,写出来的代码非常优雅,另外一方面,使用Netty之后,网络通信这块的性能问题几乎不用操心

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值