【Netty】Netty 入门案例分析 ( Netty 模型解析 | Netty 服务器端代码 | Netty 客户端代码 )

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【Netty】Netty 入门案例分析 ( Netty 模型解析 | Netty 服务器端代码 | Netty 客户端代码 )

文章目录

一、 Netty 模型代码解析

二、 Netty 案例服务器端代码

1 . 服务器主程序

2 . 服务器自定义 Handler 处理者

三、 Netty 案例客户端代码

1 . 客户端主程序

2 . 客户端自定义 Handler 处理者

四、 Netty 案例运行





一、 Netty 模型代码解析


1 . 线程池 NioEventLoopGroup :



① NioEventLoopGroup 线程池使用场景 : Netty 模型中的 BossGroup 和 WorkerGroup 都是 NioEventLoopGroup 类型的线程池 ;


② NioEventLoopGroup 默认线程个数 : 系统默认每个线程池中的 NioEventLoop 线程数是 CPU 核数 × \times× 2 , 下面的代码可以获取运行 Netty 程序的设备的 CPU 核数 ;


// 获取设备的 CPU 核数
NettyRuntime.availableProcessors()


③ 指定 NioEventLoopGroup 线程个数 : 如果不想使用 Netty 线程池的默认线程个数 , 可以在 NioEventLoopGroup 构造函数中子星设定线程数 ;


// BossGroup 线程池 : 负责客户端的连接
// 指定线程个数 : 客户端个数很少, 不用很多线程维护, 这里指定线程池中线程个数为 1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);



2 . NioEventLoopGroup 线程池线程分配 :



以客户端连接完成后 , 数据读写场景举例 ;


在 双核 CPU 的服务器上 , NioEventLoopGroup 默认有 4 44 个线程 ; 按照顺序循环分配 , 为第 n nn 个客户端分配第 n % 4 n \% 4n%4 个 NioEventLoop 线程 ;


客户端 0 00 与服务器进行数据交互在 NioEventLoop 0 00 线程中 ;

客户端 1 11 与服务器进行数据交互在 NioEventLoop 1 11 线程中 ;

客户端 2 22 与服务器进行数据交互在 NioEventLoop 2 22 线程中 ;

客户端 3 33 与服务器进行数据交互在 NioEventLoop 3 33 线程中 ;

客户端 4 44 与服务器进行数据交互在 NioEventLoop 0 00 线程中 ;

客户端 5 55 与服务器进行数据交互在 NioEventLoop 1 11 线程中 ;

客户端 6 66 与服务器进行数据交互在 NioEventLoop 2 22 线程中 ;

客户端 7 77 与服务器进行数据交互在 NioEventLoop 3 33 线程中 ;



3 . NioEventLoopGroup 线程池封装内容 :



① NioEventLoopGroup 中的若干个 NioEventLoop 线程都封装在 children 中 , 线程个数是 CPU 核数 2 倍 ;


② 每个 NioEventLoop 线程中封装了如下内容 :


选择器 ( Selector ) , 用于监听客户端的读写 IO 事件 ;

任务队列 ( taskQueue ) , 用于存储事件对应的业务逻辑任务 ;

线程执行器 ( executor ) , 用于执行线程 ;



4 . ChannelHandlerContext 通道处理者上下文对象封装内容 :



① 用户自定义的 处理者 ( Handler ) , 这里指的是 服务器端的 ServerHandr ( 自定义 ) , 客户端的 ClientHandler ( 自定义 ) ;


② 管道 ( ChannelPipeline ) : 其本质是双向链表 , 该 ChannelHandlerContext 可以获取该链表的前一个 ( prev ) , 后一个管道对象 ( next ) ;


③ 管道 与 通道 :


二者都可以通过 通道处理者上下文 ( ChannelHandlerContext ) 获取 ;

管道 与 通道 都可以互相从对方获取 ;

Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();
channel = pipeline.channel();
pipeline = channel.pipeline();


④ 管道 ( Pipeline ) 与 通道 ( Channel ) 关联 : 通过管道可以获取通道 , 通过通道也可以获取其对应的管道 ;




5 . 处理者 ( Handler ) :



① 设置 Handler : 给 WorkerGroup 线程池中的 EventLoop 线程对应的管道设置 处理器 ( Handler ) ;


② 自定义 Handler : 一般这个 Handler 都是用户自定义的类 , 继承 ChannelInboundHandlerAdapter 类 ;


③ 运行机制 : 在 BossGroup 中连接客户端成功后 , 将 NioSocketChannel 注册给 WorkerGroup 中的 EventLoop 中的 选择器 ( Selector ) , 如果监听到客户端数据 IO 事件 , 就会调用 管道 ( Pipeline ) 处理该事件 , 管道 ( Pipeline ) 中调用 处理器 ( Handler ) 处理相应的事件 , 该 处理器 ( Handler ) 可以是 Netty 提供的 , 也可以是开发者自定义的 ;



特别注意 : 自定义 Handler 中 , 重写的 ChannelInboundHandlerAdapter 方法 , 将 super() 语句都删除 ;






二、 Netty 案例服务器端代码



1 . 服务器主程序

package kim.hsl.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
 * Netty 案例服务器端
 */
public class Server {
    public static void main(String[] args) {
        // 1. 创建 BossGroup 线程池 和 WorkerGroup 线程池, 其中维护 NioEventLoop 线程
        //     NioEventLoop 线程中执行无限循环操作
        // BossGroup 线程池 : 负责客户端的连接
        // 指定线程个数 : 客户端个数很少, 不用很多线程维护, 这里指定线程池中线程个数为 1
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // WorkerGroup 线程池 : 负责客户端连接的数据读写
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // 2. 服务器启动对象, 需要为该对象配置各种参数
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup) // 设置 主从 线程组 , 分别对应 主 Reactor 和 从 Reactor
                .channel(NioServerSocketChannel.class)  // 设置 NIO 网络套接字通道类型
                .option(ChannelOption.SO_BACKLOG, 128)  // 设置线程队列维护的连接个数
                .childOption(ChannelOption.SO_KEEPALIVE, true)  // 设置连接状态行为, 保持连接状态
                .childHandler(  // 为 WorkerGroup 线程池对应的 NioEventLoop 设置对应的事件 处理器 Handler
                        new ChannelInitializer<SocketChannel>() {// 创建通道初始化对象
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                // 为 管道 Pipeline 设置处理器 Hanedler
                                ch.pipeline().addLast(new ServerHandr());
                            }
                        }
                );
        System.out.println("服务器准备完毕 ...");
        ChannelFuture cf = null;
        try {
            // 绑定本地端口, 进行同步操作 , 并返回 ChannelFuture
            cf = bootstrap.bind(8888).sync();
            System.out.println("服务器开始监听 8888 端口 ...");
            // 关闭通道 , 开始监听操作
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 出现异常后, 优雅的关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}



2 . 服务器自定义 Handler 处理者

package kim.hsl.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
/**
 * Handler 处理者, 是 NioEventLoop 线程中处理业务逻辑的类
 *
 * 继承 : 该业务逻辑处理者 ( Handler ) 必须继承 Netty 中的 ChannelInboundHandlerAdapter 类
 * 才可以设置给 NioEventLoop 线程
 *
 * 规范 : 该 Handler 类中需要按照业务逻辑处理规范进行开发
 */
public class ServerHandr extends ChannelInboundHandlerAdapter {
    /**
     * 读取数据 : 在服务器端读取客户端发送的数据
     * @param ctx
     *      通道处理者上下文对象 : 封装了 管道 ( Pipeline ) , 通道 ( Channel ), 客户端地址信息
     *      管道 ( Pipeline ) : 注重业务逻辑处理 , 可以关联很多 Handler
     *      通道 ( Channel ) : 注重数据读写
     * @param msg
     *      客户端上传的数据
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 查看 ChannelHandlerContext 中封装的内容
        System.out.println("channelRead : ChannelHandlerContext ctx = " + ctx);
        // 将客户端上传的数据转为 ByteBuffer
        // 这里注意该类是 Netty 中的 io.netty.buffer.ByteBuf 类
        // 不是 NIO 中的 ByteBuffer
        // io.netty.buffer.ByteBuf 性能高于 java.nio.ByteBuffer
        ByteBuf byteBuf = (ByteBuf) msg;
        // 将 ByteBuf 缓冲区数据转为字符串, 打印出来
        System.out.println(ctx.channel().remoteAddress() + " 接收到客户端发送的数据 : " + 
          byteBuf.toString(CharsetUtil.UTF_8));
    }
    /**
     * 服务器端读取数据完毕后回调的方法
     * @param ctx
     *      通道处理者上下文对象 : 封装了 管道 ( Pipeline ) , 通道 ( Channel ), 客户端地址信息
     *      *      管道 ( Pipeline ) : 注重业务逻辑处理 , 可以关联很多 Handler
     *      *      通道 ( Channel ) : 注重数据读写
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 数据编码 : 将字符串编码, 存储到 io.netty.buffer.ByteBuf 缓冲区中
        ByteBuf byteBuf = Unpooled.copiedBuffer("Hello Client", CharsetUtil.UTF_8);
        // 写出并刷新操作 : 写出数据到通道的缓冲区 ( write ), 并执行刷新操作 ( flush )
        ctx.writeAndFlush(byteBuf);
    }
    /**
     * 异常处理 , 上面的方法中都抛出了 Exception 异常, 在该方法中进行异常处理
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("通道异常, 关闭通道");
        //如果出现异常, 就关闭该通道
        ctx.close();
    }
}







三、 Netty 案例客户端代码



1 . 客户端主程序

package kim.hsl.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
    public static void main(String[] args) {
        // 客户端只需要一个 时间循环组 , 即 NioEventLoopGroup 线程池
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        // 客户端启动对象
        Bootstrap bootstrap = new Bootstrap();
        // 设置相关参数
        bootstrap.group(eventLoopGroup)     // 设置客户端的线程池
                .channel(NioSocketChannel.class)    // 设置客户端网络套接字通道类型
                .handler(   // 设置客户端的线程池对应的 NioEventLoop 设置对应的事件处理器 Handler
                        new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new ClientHandr());
                            }
                        }
                );
        try {
            // 开始连接服务器, 并进行同步操作
            // ChannelFuture 类分析 , Netty 异步模型
            // sync 作用是该方法不会再次阻塞
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
            System.out.println("客户端连接服务器成功 ...");
            // 关闭通道, 开始监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 优雅的关闭
            eventLoopGroup.shutdownGracefully();
        }
    }
}





2 . 客户端自定义 Handler 处理者

package kim.hsl.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
 * Handler 处理者, 是 NioEventLoop 线程中处理业务逻辑的类
 *
 * 继承 : 该业务逻辑处理者 ( Handler ) 必须继承 Netty 中的 ChannelInboundHandlerAdapter 类
 * 才可以设置给 NioEventLoop 线程
 *
 * 规范 : 该 Handler 类中需要按照业务逻辑处理规范进行开发
 */
public class ClientHandr extends ChannelInboundHandlerAdapter {
    /**
     * 通道就绪后触发该方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 查看 ChannelHandlerContext 中封装的内容
        System.out.println("channelActive : ChannelHandlerContext ctx = " + ctx);
        // 数据编码 : 将字符串编码, 存储到 io.netty.buffer.ByteBuf 缓冲区中
        ByteBuf byteBuf = Unpooled.copiedBuffer("Hello Server", CharsetUtil.UTF_8);
        // 写出并刷新操作 : 写出数据到通道的缓冲区 ( write ), 并执行刷新操作 ( flush )
        ctx.writeAndFlush(byteBuf);
        System.out.println("客户端向服务器端发送 Hello Server 成功");
    }
    /**
     * 读取数据 : 在服务器端读取客户端发送的数据
     * @param ctx
     *      通道处理者上下文对象 : 封装了 管道 ( Pipeline ) , 通道 ( Channel ), 客户端地址信息
     *      管道 ( Pipeline ) : 注重业务逻辑处理 , 可以关联很多 Handler
     *      通道 ( Channel ) : 注重数据读写
     * @param msg
     *      服务器返回的数据
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 查看 ChannelHandlerContext 中封装的内容
        System.out.println("channelRead : ChannelHandlerContext ctx = " + ctx);
        // 将服务器下发的数据转为 ByteBuffer
        // 这里注意该类是 Netty 中的 io.netty.buffer.ByteBuf 类
        // 不是 NIO 中的 ByteBuffer
        // io.netty.buffer.ByteBuf 性能高于 java.nio.ByteBuffer
        ByteBuf byteBuf = (ByteBuf) msg;
        // 将 ByteBuf 缓冲区数据转为字符串, 打印出来
        System.out.println(ctx.channel().remoteAddress() + " 服务器返回的数据 : " + 
          byteBuf.toString(CharsetUtil.UTF_8));
    }
    /**
     * 异常处理 , 上面的方法中都抛出了 Exception 异常, 在该方法中进行异常处理
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("通道异常, 关闭通道");
        //如果出现异常, 就关闭该通道
        ctx.close();
    }
}







四、 Netty 案例运行


1 . 运行服务器端 : 服务器启动 , 监听 8888 端口 ;


image.png



2 . 运行客户端 : 客户端连接服务器的 8888 端口 , 并向服务器端写出 Hello Server 字符串 , 之后便接到服务器端回送的 Hello Client 字符串信息 ;

image.png



3 . 查看客户端 : 服务器端接收到客户端信息 , 向客户端写出 Hello Client 字符串 ;

image.png


目录
相关文章
|
3天前
|
NoSQL Java Linux
《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结
《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结
122 75
|
4天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
38 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
2天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
20天前
|
存储 监控 调度
云服务器成本优化深度解析与实战案例
本文深入探讨了云服务器成本优化的策略与实践,涵盖基本原则、具体策略及案例分析。基本原则包括以实际需求为导向、动态调整资源、成本控制为核心。具体策略涉及选择合适计费模式、优化资源配置、存储与网络配置、实施资源监控与审计、应用性能优化、利用优惠政策及考虑多云策略。文章还通过电商、制造企业和初创团队的实际案例,展示了云服务器成本优化的有效性,最后展望了未来的发展趋势,包括智能化优化、多云管理和绿色节能。
|
26天前
|
测试技术 开发者 Python
使用Python解析和分析源代码
本文介绍了如何使用Python的`ast`模块解析和分析Python源代码,包括安装准备、解析源代码、分析抽象语法树(AST)等步骤,展示了通过自定义`NodeVisitor`类遍历AST并提取信息的方法,为代码质量提升和自动化工具开发提供基础。
43 8
|
24天前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
44 4
|
23天前
|
PHP 开发者 容器
PHP命名空间深度解析:避免命名冲突与提升代码组织####
本文深入探讨了PHP中命名空间的概念、用途及最佳实践,揭示其在解决全局命名冲突、提高代码可维护性方面的重要性。通过生动实例和详尽分析,本文将帮助开发者有效利用命名空间来优化大型项目结构,确保代码的清晰与高效。 ####
20 1
|
1月前
|
机器学习/深度学习 存储 人工智能
强化学习与深度强化学习:深入解析与代码实现
本书《强化学习与深度强化学习:深入解析与代码实现》系统地介绍了强化学习的基本概念、经典算法及其在深度学习框架下的应用。从强化学习的基础理论出发,逐步深入到Q学习、SARSA等经典算法,再到DQN、Actor-Critic等深度强化学习方法,结合Python代码示例,帮助读者理解并实践这些先进的算法。书中还探讨了强化学习在无人驾驶、游戏AI等领域的应用及面临的挑战,为读者提供了丰富的理论知识和实战经验。
55 5
|
2天前
|
人工智能 JSON Linux
利用阿里云GPU加速服务器实现pdf转换为markdown格式
随着AI模型的发展,GPU需求日益增长,尤其是个人学习和研究。直接购置硬件成本高且更新快,建议选择阿里云等提供的GPU加速型服务器。
利用阿里云GPU加速服务器实现pdf转换为markdown格式
|
1天前
|
开发框架 缓存 .NET
阿里云轻量应用服务器、经济型e、通用算力型u1实例怎么选?区别及选择参考
在阿里云目前的活动中,价格比较优惠的云服务器有轻量应用服务器2核2G3M带宽68元1年,经济型e实例2核2G3M带宽99元1年,通用算力型u1实例2核4G5M带宽199元1年,这几个云服务器是用户关注度最高的。有的新手用户由于是初次使用阿里云服务器,对于轻量应用服务器、经济型e、通用算力型u1实例的相关性能并不是很清楚,本文为大家做个简单的介绍和对比,以供参考。

推荐镜像

更多