netty的简单使用

介绍:

netty是一个网络框架,截个简图。对比了官网和github上的介绍,还是github上的介绍比较简洁。

其实就一句话,翻译过来就是:Netty是一个事件驱动的异步网络应用程序框架。在直白点就是一个封装了nio的异步网络框架。详细介绍可以参考官网:

netty.io/

github.com/netty/netty

以下为模版示例代码的使用:

示例代码:

引入依赖:

xml 复制代码
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.77.Final</version>
</dependency>

服务端启动模版:

java 复制代码
public class NettyService {
    
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    //这两个是netty自带的编码器,也可以自己实现自定义的编码器
                                    .addLast(new StringDecoder())
                                    .addLast(new StringEncoder())
                                    //添加此参数后(这里是60秒),如果服务端60秒内没有响应数据,则ServerHandler类里的userEventTriggered方法会被调用
                                    .addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS))
                                    //自定义netty数据处理类,在类中实现逻辑
                                    .addLast("serverHandler",new ServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            //绑定ip和端口,sync方法会让当前线程阻塞,直到绑定操作完成
            ChannelFuture future = bootstrap.bind("127.0.0.1", 8888).sync();
            System.out.println("启动服务端,ip:" + "127.0.0.1" + ",端口:" + 8888);

            // 获取已成功建立连接或者已绑定的Channel的实例,用于进行数据读写等后续操作。
            Channel channel = future.channel();
            //调用关闭逻辑,这里在服务关闭时被触发,确保资源释放完毕且程序能按预期顺序执行后续逻辑
            channel.closeFuture().sync();
            //    上诉两个可以这样写future.channel().closeFuture().sync();
        } catch (Exception e) {
            System.out.println("服务器启动错误:" + e);
        } finally {
            //服务停止后关闭事件循环组
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

    }
}

服务端netty数据处理类,对应NettyService类中的这个对象:

java 复制代码
/**
 *
 *继承的SimpleChannelInboundHandler类中可以是任意自定义类型,这里为方便演示就用string类型了
 */
public class ServerHandler extends SimpleChannelInboundHandler<String> {


    /**
     * 处理任何异常的入站或出站事件
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("执行exceptionCaught方法 --- 关闭服务端连接");
        //发生异常,关闭通道
        ctx.close();
    }

    /**
     * 1
     * 该方法可以设置 channel 的属性或者注册其他监听事件
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行handlerAdded方法 --- 注册监听事件或属性");
        super.handlerAdded(ctx);
    }


    /**
     * 2
     * 在客户端和服务端的网络连接建立完成时触发
     *
     */
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行channelRegistered方法--成功注册服务并绑定端口连接");
        super.channelRegistered(ctx);
    }

    /**
     * 3
     * 在客户端和服务端的网络连接建立完成且可以开始读写数据时触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行channelActive方法--成功建立两端连接");
        super.channelActive(ctx);
    }

    /**
     * 4
     *  接收到一个新的入站数据块
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //获取客户端发送过来的消息
        System.out.println("执行channelRead0方法---收到客户端" + ctx.channel().remoteAddress() + "发送的消息:" + msg);
        //ctx.fireUserEventTriggered(null);
    }

    /**
     * 5
     * 收到客户端发送消息后,可以通过此方法回传客户端发送的消息
     * 客户端连接中断时,该方法也会被执行
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行channelReadComplete方法---回传服务端的消息");
        //接收到客户端消息后,回传消息给客户端。Unpooled是netty里的工具类,可以指定编码格式并将数据换为字节数组
        ctx.writeAndFlush(Unpooled.copiedBuffer("我是服务端,滴滴滴滴", CharsetUtil.UTF_8));
    }

    /**
     * 6
     * 服务端和客户端连接时,客户端第一次发生消息后。每隔几秒时间,会被调用。
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("执行userEventTriggered方法---自定义事件被触发");
        super.userEventTriggered(ctx, evt);
    }

    /**
     * 7
     * 在客户端和服务端的网络连接断开或关闭时,通道从活动状态变为非活动状态后触发。
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行channelInactive方法---两端连接已断开");
        super.channelInactive(ctx);
    }

    /**
     * 8
     * 在客户端或服务端连接时,如果有某一端断开连接,则此方法会被调用
     * 此方法在handlerRemoved之前调用
     */
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行channelUnregistered方法---" + ctx.channel().localAddress() + "-服务端没有接收到任何数据");
        super.channelUnregistered(ctx);
    }



    /**
     * 9
     *
     * 当一个ChannelHandler实例从ChannelPipeline中移除时
     * 主要用于释放资源或清理工作
     * 两个方式触发这个方法:
     * 1 显示调用 ctx.pipeline().remove(this)方法才会触发这个方法执行
     * 2 在用户断开连接时,也会默认执行这个方法
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行handlerRemoved方法---" + ctx.channel().localAddress() + "-两端服务已断开连接");
        super.handlerRemoved(ctx);
    }



    /**
     * 根据数据可读变可写或可写变可读时触发
     * 当 Channel 的写入能力发生变化时(例如从可写变为不可写,或者从不可写变为可写),netty 会调用该方法。
     *
     */
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        super.channelWritabilityChanged(ctx);
    }
}

netty处理逻辑的方法顺序如下图:

客户端通用模板:

java 复制代码
public class NettyClient {

    public static void main(String[] args) {
        //NIO事件对象
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //获取启动类对象
            Bootstrap  bootstrap = new Bootstrap();
            //添加到启动类里
            bootstrap.group(eventExecutors)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    //这两个是netty自带的编码器,也可以自己实现自定义的编码器
                                    .addLast(new StringDecoder())
                                    .addLast(new StringEncoder())
                                    //添加此参数后(这里是60秒),如果服务端5秒内没有响应数据,则ServerHandler类里的userEventTriggered方法会被调用
                                    .addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS))
                                    //自定义netty数据处理类,在类中实现逻辑
                                    .addLast("clientHandler",new ClientHandler());
                        }
                    });


            //连接服务端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
            System.out.println("启动客户端,ip:" + "127.0.0.1" + ",端口:" + 8888);
            //获取已成功建立连接的Channel,并对通道关闭进行监听
            channelFuture.channel().closeFuture().sync();
          
        } catch (InterruptedException e) {
            System.out.println("客户端启动错误:" + e);
        } finally {
            //服务停止后关闭事件循环组
            eventExecutors.shutdownGracefully();
        }
    }
}

客户端netty数据处理类,对应ClientHandler类中的这个对象,因服务端已详细介绍方法细节,客户端处理类则不在赘述:

scala 复制代码
public class ClientHandler extends SimpleChannelInboundHandler<String> {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //接收服务端发送过来的消息
        System.out.println("收到服务端" + ctx.channel().remoteAddress() + "的消息:" + msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //发送消息到服务端
        ctx.writeAndFlush(Unpooled.copiedBuffer("我是客户端,滴滴滴滴~", CharsetUtil.UTF_8));

    }


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("关闭客户端连接");

        ctx.close();
    }

以上处理类中,一般常用的方法有如下4个,其他方法则根据业务自行扩展:

java 复制代码
/**
 * 
 * 处理入栈消息
 */
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
    
}

/**
 * 
 * 处理异常
 */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    super.exceptionCaught(ctx, cause);
}

/**
 * 
 * 在客户端和服务端的网络连接断开或关闭时,通道从活动状态变为非活动状态后触发。
 */
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    super.channelInactive(ctx);
}
/**
 * 
 * 在客户端和服务端的网络连接建立完成且可以开始读写数据时触发
 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    super.channelActive(ctx);
}

测试

先启动服务端,再启动客户端。控制台会打印如下内容:

类结构依赖图

下图是netty的类结构树,主要关注3个类:

  1. ServerBootstrap(服务端启动类)
  2. bootstrap(客户端启动类)
  3. NioEventLoopGroup(线程池和事件循环的容器)

解析

Bootstrap 和 ServerBootstrap 分别用于客户端和服务器端的引导配置。它们都继承了 AbstractBootstrap 类,但它们在功能和使用场景上有所区别:

  1. ServerBootstrap:用来初始化和配置 Netty 服务器端,并绑定本地端口监听来自客户端的连接请求。它需要配置两个 EventLoopGroup:一个是 bossGroup,用来接收新的连接请求;另一个是 workerGroup,用于处理已经建立连接的客户端的读写事件。
  2. Bootstrap:用来初始化和配置客户端,创建并连接到远程服务端。它和服务端一样也需要绑定IP端口,才能自把数据发送到指定位置并被服务端进行监听处理。
  3. NioEventLoopGroup:可以理解为线程池和数据流转的中介。如果不设置线程数,则默认是本机CPU的2倍数量。
java 复制代码
//代码示例 -- 也就是如果不设置这个4,如果不设置就是使用本机CPU * 2 的数量
NioEventLoopGroup eventExecutors = new NioEventLoopGroup(4);


以下是一段源码:

private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
//判断是否是默认的,如果是则使用上诉DEFAULT_EVENT_LOOP_THREADS配置的数量
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

总结

  1. 本章列出了一些通用代码模板,只支持入门操作使用,这些模版小伙伴可以按实际场景进行封装。
  2. 本章只是简单介绍了使用示例,详细类流程解析在下一章节。

类结构解析篇(未完成)

相关推荐
刘大辉在路上2 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
追逐时光者4 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~4 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581364 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳4 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾5 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭5 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding6 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者6 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu