学习使用netty

本文详细介绍了Netty,一个高性能的异步事件驱动的NIO框架,包括Netty的特性、与BIO和NIO的区别、核心组件如Channel、EventLoop、ChannelHandler和ChannelPipeline的详解,以及引导程序ServerBootstrap和Bootstrap的使用。Netty在高并发场景下表现出色,通过内存池、零拷贝等技术提升了性能,并提供了丰富的API和解码器/编码器支持。

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

学习使用netty

netty详细介绍1:https://www.infoq.cn/article/netty-high-performance/#anch111813
netty详细介绍2:https://blog.csdn.net/sun7545526/category_7685695.html
netty使用:https://blog.csdn.net/haoyuyang/article/details/53243785

1. netty简介

Netty 是一个高性能、异步事件驱动的 NIO 框架,它提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。

作为当前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于 Netty 的 NIO 框架构建。

Netty有很多重要的特性,主要特性如下:

  • 优雅的设计
  • 统一的API接口,支持多种传输类型,例如OIO,NIO
  • 简单而强大的线程模型
  • 丰富的文档
  • 卓越的性能
  • 拥有比原生Java API 更高的性能与更低的延迟
  • 基于池化和复用技术,使资源消耗更低
  • 安全性
  • 完整的SSL/TLS以及StartTLS支持
  • 可用于受限环境,如Applet以及OSGI

Netty的以上特性,比较适合客户端数据较大的请求/处理场景,例如web服务器等,要想知道有哪些系统使用了Netty,可以参考:http://netty.io/wiki/adopters.html

2. bio和nio已经netty

BIO:阻塞io:在早期的java中使用:
https://blog.csdn.net/sqlgao22/article/details/102858119

NIO:非阻塞io:在java4引入
https://blog.csdn.net/sqlgao22/article/details/103087676
NIO相比于BIO,该模型有以下特点:
1.使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销
2.当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务.

虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并
不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,此时就时Netty上场的时间了。

netty
Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升
1.使用多路复用技术,提高处理连接的并发性
2.零拷贝:Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝,并且Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
3.Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
4. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
5.使用主从Reactor多线程模型,提高并发性
6.采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
7. 默认使用Protobuf的序列化框架
8. 灵活的TCP参数配置

详细说明,可参考: http://www.infoq.com/cn/articles/netty-high-performance#anch111813

3. netty组件

Netty中主要组件包括:

  • Channel:代表了一个链接,与EventLoop一起用来参与IO处理。
  • ChannelHandler:为了支持各种协议和处理数据的方式,便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。(主要的内容)
  • ChannelPipeline:提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站
    和出站事件流的 API。
  • EventLoop:Channel处理IO操作,一个EventLoop可以为多个Channel服务。
  • EventLoopGroup:会包含多个EventLoop。
    在这里插入图片描述
    1.一个 EventLoopGroup 包含一个或者多个 EventLoop;
    2.一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
    3.所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
    4.一个 Channel 在它的生命周期内只注册于一个 EventLoop,而EventLoop可以处理多个channel数据;
    5.一个Channel对应一个ChannelPipeline(管理ChannelHandle)。
    6.一个ChannelPipeline中可以注册多个ChannelHandle.
    7.一个Channel可以使用多个ChannelHandle.
3.1 Channel

在Java中(Socket类),基本的 I/O 操作(bind()、 connect()、 read()和 write())依赖于底层网络传输所提供的功能。 Netty 的 Channel 接
口所提供的 API降低了直接使用 Socket 类的复杂性。Netty中也提供了使用多种方式连接的Channel:

  • EmbeddedChannel;
  • LocalServerChannel;
  • NioDatagramChannel;
  • NioSctpChannel;
  • NioSocketChannel。
3.2 EventLoop&EventLoopGroup

Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端 Channel.EventLoop主要用于处理连接的生命周期中所发生的事件

3.3 ChannelHandler&ChannelHandlerPipline

ChannelHandler
对于开发一个Netty的应用而言,主要开发的组件可能就是 ChannelHandler, 它充当了所有处理入站和出站数据的应用程序逻辑的容器,根据数据流向的不同,ChannelHandler可以分为ChannelInboundHandler以及ChannelOutboundHandler.
如果将两个类别的 ChannelHandler都混合添加到同一个 ChannelPipeline 中时,Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。

对于ChannelHandler的实现类而言,可能不需要关注事件处理周期的每个环节,如果要把Inbound或是Outboud接口的每个方法都实现,就会额外的带来很多的工作量,Netty对于该种情况提供了几种Adapter的解决方案:
– ChannelHandlerAdapter
– ChannelInboundHandlerAdapter
– ChannelOutboundHandlerAdapter
– ChannelDuplexHandler

ChannelPipeline
ChannelPipeline是管理ChannelHandle的容器,由于一个channel可以使用多个ChannelHandle,所以使用ChannelPipeline进行管理.
使用过程:
1.创建一个ChannelInitializer对象.
2.重写ChannelInitializer.initChannel()方法.
3.在ChannelInitializer.initChannel()方法中将自定义的ChannelHandle注册上.
4.将ChannelInitializer对象注册到Bootstrap配置类上.

3.4 解码器/编码器

当通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。
入站消息会被解码:从字节转换为另一种格式,通常是一个 Java 对象。
如果是出站消息,则会发生编码:将从它的当前格式被编码为字节。

Netty默认已经提供了一堆的编/解码器,一般需求已经可以满足,如果不满足可以通继承ByteToMessageDecoder 或 MessageToByteEncoder来实现自己的编/解码器。通过查看类的继承结构可以看出 Netty 提供的编码器/解码器适配器类都实现了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口。

对于解码器Handler而言,其重写了channelRead方法,对于每个从入站
Channel 读取的消息,这个方法都将会被调用。随后,它将调用由解码器所提供的 decode()方法,并将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。OutboundHandler采用相反的处理方式。

3.5 引导程序ServerBootstrap/Bootstrap

可以理解为netty的配置类.
ServerBootstrap是服务端配置.
Bootstrap是客户端配置.

需要注意的是,引导一个客户端只需要一个 EventLoopGroup(workGroup);

但是一个ServerBootstrap(服务端) 则需要两个(bossGroup和workerGroup).第一个EventLoopGroup用来专门负责绑定到端口监听连接事件,而把第二个EventLoopGroup用来处理每个接收到的连接。对于Server端,如果仅由一个EventLoopGroup处理所有请求和连接的话,在并发量很大的情况下,这个EventLoopGroup有可能会忙于处理已经接收到的连接而不能及时处理新的连接请求,用两个的话,会有专门的线程来处理连接请求,不会导致请求超时的情况,大大提高了并发处理能力.

4. netty使用

4.1 服务端
public class ServerTest {
    public static void main(String[] args) {
        //用于处理client的连接
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //用于处理每一个连接
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            //创建一个辅助工具类,帮助进行网络的配置
            ServerBootstrap bootstrap = new ServerBootstrap();
            //注册两个连接,意思是:这两个连接就使用当前的bootstrap的配置
            bootstrap.group(bossGroup, workGroup);
            //指定连接是nio模式(还有oio,epoll,local等模式)
            bootstrap.channel(NioServerSocketChannel.class);
            //配置数据具体的处理
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    //获取管道
                    ChannelPipeline channelPipeline = socketChannel.pipeline();
                    //这里一定要配置编码和解码器,否则字符串消息无法识别
                    //字符串解码器
                    channelPipeline.addLast(new StringDecoder());
                    //字符串编码器
                    channelPipeline.addLast(new StringEncoder());
                    //配置数据具体处理类ChannelInboundHandlerAdapter
                    channelPipeline.addLast(new ServerChannelHandel());
                }
            });

            //设置TCP参数(这里有个对参数的解释)
            //1.链接缓冲池的大小(ServerSocketChannel的设置)
            bootstrap.option(ChannelOption.SO_BACKLOG,1024);
            //维持链接的活跃,清除死链接(SocketChannel的设置)
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
            //关闭延迟发送
            bootstrap.childOption(ChannelOption.TCP_NODELAY,true);

            //绑定端口
            ChannelFuture future = bootstrap.bind(8888).sync();
            System.out.println("服务端开始接受请求");
            //等待服务端监听端口关闭
            future.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅的关闭
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

class ServerChannelHandel extends ChannelInboundHandlerAdapter {
    //服务端读取客户端发来的请求
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String byteBuf = (String) msg;
        System.out.println("服务端收到的消息是==" + byteBuf);
        ctx.channel().writeAndFlush("收到了消息");
    }
    //也可重写客户端断开连接channelInActive方法/以及异常处理方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("这里是收到客户端连接后执行的方法----channelActive");
    }
}

/**
 * 对于ChannelOption.SO_BACKLOG的解释:
 * 客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),
 * 服务器端接收到客户端发送的SYN时,向客户端发送SYN+ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,
 * 最后客户端回复服务器ACK,当服务端接收到后就建立连接(第三次握手),此时TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。
 * 也就是说accept从B队列中取出完成了三次握手的连接。A队列和B队列的长度之和就是backlog。
 * 当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
 * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。
 * 要注意的是,backlog对程序支持的连接数并无影响,backlog影响的只是还没有被accept取出的连接
*/

4.2 客户端

public class ClientTest {
    public static void main(String[] args){
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(worker);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    ChannelPipeline channelPipeline = socketChannel.pipeline();
                    channelPipeline.addLast(new StringDecoder());
                    channelPipeline.addLast(new StringEncoder());
                    channelPipeline.addLast(new ClientChannelHandel());
                }
            });
            
            //发起异步连接操作
            ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8888)).sync();
            //等待客户端链路关闭
            futrue.channel().closeFuture().sync();
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            worker.shutdownGracefully();
        }
    }
}

class ClientChannelHandel extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String byteBuf = (String) msg;
        System.out.println("服务端回复=="+byteBuf);
    }
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("这里是与服务端建立连接后执行的方法(发送数据)----channelActive");
        Channel channel = ctx.channel();
        channel.writeAndFlush("第一个使用netty发的消息");
    }
}
4.3 执行结果

服务端输出:
在这里插入图片描述
客户端输出:
在这里插入图片描述

5.netty其他

netty的数据传输:https://blog.csdn.net/eric_sunah/article/details/80436184
netty的数据模型:https://blog.csdn.net/eric_sunah/article/details/80437025
netty的数据容器:https://blog.csdn.net/eric_sunah/article/details/80497831

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值