Netty入门,看这篇就够了

本文介绍了Netty在解决NIO复杂性问题上的封装,阐述了其作为高性能通讯框架的优势,包括线程模型、BossGroup和WorkerGroup的作用,以及如何通过pipeline和handler进行开发。还提供了入门应用的代码示例和handler的常见用法,包括心跳机制的实现。

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

Netty的基本理解

我们知道NIO以及多路复用的出现解决了BIO连接的阻塞和获取信息的阻塞问题。

那Netty的出现又是为了解决什么样的痛点问题呢?NIO的API极其繁杂,需要熟练运用Selector、 ServerSocketChannel、 SocketChannel、ByteBuffer等,导致开发难度也不小。而Netty是基于JDK的NIO之上,进行了封装,大大降低了开发的难度,让我们可以就基于handler来进行开发。

作为异步高性能的通讯框架,拥有高性能,高吞吐,低延迟,长连接,资源消耗低,最小化不必要的内存复制等优点,并提供了TCP/UDP和HTTP的协议栈,它的应用极其之广,Dubbo的RPC框架,Rocketmq,很多游戏的客户端与服务器之间的连接,等等,都是基于Netty来作为节点间通信的。

Netty线程模型

看懂这个图首先简单回顾一下多路复用模型。多路复用通过使用非阻塞I/O和选择器(Selectors),允许一个线程监视多个连接I/O事件,并提供多线程甚至是线程池来轮询(Select/Poll)或者监听(Epoll)数据的读取和写入。

接着再来欣赏一下这张图(图是网上找的,非原创),是不是清晰了很多。

先理解一下理论,在后续会提供Netty应用的代码详解,里面都有对应到。

  1. BossGroup就是专门负责接受客户端连接,WorkerGroup专门负责网络的读写操作,

  2. 它们都是属于NioEventLoopGroup,是一种事件循环的线程池,在这个线程池中,会有NioEventLoop(相当于worker thread),它会包含一个selector,用于监听并作出回应。

  3. Boss线程里面会处理accept事件(与Client建立连接),并注册将NIOSocketChannel到worker线程的selector上

  4. Worker线程会轮询注册到selector上的所有NIOSocketChannel的读写事件,即业务。

  5. 最后橙色的就是pipeline(管道),上面维护了很多handler,每一个handler都是具体的业务逻辑,也是我们去进行具体开发的地方。handler又分为入栈和出栈,也存在着一定的执行顺序。

看了那么多的理论,是不是有点迫不及待去用它了。下面来看看具体的应用代码吧~

Netty的入门应用

Netty的基础框架

其实server端和client端的写法都是基本固定的,具体可操作的就是加入到pipeline的handler,如编解码的handler(会比较得影响性能),以及实现具体业务逻辑的handler,它通过责任链的模式保证了handler执行的顺序。

Server启动代码

public class NettyServer {

   public static void main(String[] args) {

       //两个线程池。boss负责Accept事件,即处理连接请求

       NioEventLoopGroup boss = new NioEventLoopGroup();

       //worker负责轮询Read, Write 事件,即真正的业务逻辑处理

       NioEventLoopGroup worker = new NioEventLoopGroup();

       try {

           //server端的启动对象

           ServerBootstrap serverBootstrap = new ServerBootstrap();

           //server使用NioServerSocketChannel作为server的通道实现,比较常见。

           //还可以使用EpollServerSocketChannel,但仅限与linux操作系统。

           serverBootstrap.channel(NioServerSocketChannel.class);

           //和两个线程池绑定

           serverBootstrap.group(boss, worker);

           serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

               @Override

               protected void initChannel(SocketChannel ch) throws Exception {

                   //首先要考虑编解码以及TCP的粘包,半包问题

                   ch.pipeline().addLast(new ProcotolFrameDecoder());

                   ch.pipeline().addLast(new MessageCodecSharable());

                   //业务代码

                   ch.pipeline().addLast(new HelloWorldHandler());

               }

           });

           //绑定端口

           Channel channel = serverBootstrap.bind(8080).sync().channel();

           //对通道的关闭进行监听,是异步操作。sync方法会等待通道关闭处理完成

           channel.closeFuture().sync();

       } catch (InterruptedException e) {

           e.printStackTrace();

       } finally {

           boss.shutdownGracefully();

           worker.shutdownGracefully();

       }

   }

}

其实除了业务代码那一块,其他的都基本一样的,初始化灰常简单。

Client启动代码

public class NettyClient {

   public static void main(String[] args) {

       //事件循环组

       NioEventLoopGroup group = new NioEventLoopGroup();

       try {

           Bootstrap bootstrap = new Bootstrap();

           bootstrap.channel(NioSocketChannel.class);

           bootstrap.group(group);

           bootstrap.handler(new ChannelInitializer<SocketChannel>() {

               @Override

               protected void initChannel(SocketChannel ch) throws Exception {

                   //一样,首先要考虑编解码以及TCP的粘包,半包问题

                   ch.pipeline().addLast(new ProcotolFrameDecoder());

                   ch.pipeline().addLast(new MessageCodecSharable());

                   //业务处理的handler

                   ch.pipeline().addLast(new PingHandler());

               }

           });

           //启动client去连接server服务器

           Channel channel = bootstrap.connect("localhost", 8080).sync().channel();

           channel.closeFuture().sync();

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           group.shutdownGracefully();

       }

   }

}

部分充分的代码就没有注释了,和server也一样,主要是在handler的处理。

handler的常见用法

首选需要清醒的一点是,handler们都被安置在pipeline里面。pipeline是一个双向链表,分入栈handler(会从链表的head往后传递到最后一个入站的handler)和出栈handler(从链表的tail往前传递到最前一个出栈的handler),两种类型的handler互不干扰。

这里列举了最最最常用的Handler,仅限于快速入门,需要更高级用法的请参考官网:Netty API Reference (4.1.109.Final)

下图是简单入栈的类图关系

当然别忘记了,子类也可以重写父类的方法,此外,通过writeAndFlush(Message)也可以快速将消息发送出去:

Handler 类

方法名

方法介绍

ChannelHandlerAdapter

handlerAdded

当Handler被添加到Pipeline时调用。

handlerRemoved

当Handler从Pipeline中移除时调用。

exceptionCaught

在处理过程中发生异常时调用。

ChannelInboundHandlerAdapter

channelRead

每当从客户端接收到新数据时调用。数据的类型通常是ByteBuf。

channelActive

当Channel处于活跃状态(已连接到它的远程节点)时调用。

channelInactive

当Channel不再连接其远程节点时调用。

channelReadComplete

当Channel上的一次数据读取完毕时调用。

SimpleChannelInboundHandler<Message>

channelRead0

该方法与channelRead类似,但它只处理特定类型的Message。

下图是简单出栈的类图关系:

出栈的看上去会更简单,没那么复杂的项目很少依赖于出栈handler,一般都在入栈handler里面通过write()或者writeAndFlush()就直接输出了。

ChannelOutboundHandlerAdapter

write

被请求将消息写入到客户端时调用。

flush

请求将之前写入到Channel中的消息全部写入到网络Socket中。

bind

当请求将Channel绑定到本地地址时调用。

connect

当请求将Channel连接到远程节点时调用。

disconnect

当请求将Channel从其远程节点断开连接时调用。

close

当请求关闭Channel时调用。

还有一些工具类的handler,它们具备协助我们业务代码的能力,也是需要认识一下滴~

Handler类

方法

描述

ByteToMessageDecoder

decode

解析接收到的ByteBuf数据,转换成一个或多个Java对象。

decodeLast

在Channel的最后一次调用decode方法时,处理剩余数据。

MessageToByteEncoder

encode

接受一个消息并将其编码为ByteBuf,然后写入到网络中。

IdleStateHandler

构造方法:

IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds)

该类不提供特定的方法供重写,但它会在检测到读、写或读写空闲时触发userEventTriggered事件,如

IdleState.WRITER_IDLE 或

IdleState.READER_IDLE。

捕获该类事件再进行处理,如心跳机制。

心跳机制如下:

Client

//先写一个心跳的handler

public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
    
    // 心跳消息内容,也可以用具体的Message对象来代替

    private static final String HEARTBEAT_SEQUENCE = "HEARTBEAT";  


    @Override

    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        if (evt instanceof IdleStateEvent) {

            IdleStateEvent event = (IdleStateEvent) evt;

            //写空闲了,就发送心跳包

            if (event.state() == IdleState.WRITER_IDLE) {

                // 发送心跳消息

                System.out.println("Sending heartbeat to server...");

                ctx.writeAndFlush(HEARTBEAT_SEQUENCE);

            }

        } else {

            super.userEventTriggered(ctx, evt);

        }

    }

}


//在client端添加

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new IdleStateHandler(0, 3, 0, TimeUnit.SECONDS));

pipeline.addLast(new HeartbeatHandler());

Server

//server端再开一个IdleStateHandler

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new IdleStateHandler(6, 0, 0, TimeUnit.SECONDS));

pipeline.addLast(new HeartbeatFailHandler());

如果没有收到来自client的心跳或者其他的信息,会再触发这里的读空闲事件(IdleState.READER_IDLE)。在HeartbeatFailHandler里面监听这类事件再根据业务逻辑进行处理就好了(关闭or重连)。
 

Netty的入门教学就先说这么多,后面可能会再做一节源码分析~



 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值