Netty 核心原理与实战:从 DiscardServer 看透 Reactor 模式与组件协作

目录

Netty 是什么?

Netty 的目标

Netty 实战案例 DiscardServer

服务端程序 NettyDiscardServer

业务处理器 NettyDiscardHandler

配置类 NettyDemoConfig

回顾 Reactor 模式中的 IO 事件处理流程

Netty 中的 Channel

Netty 中的 Reactor

Netty 中的 Handler

Netty 中的 Pipeline


Netty 是什么?

        Netty 是一个基于 Java NIO 的客户端/服务器端通信框架,旨在简化网络编程的开发过程。它不仅支持高并发、高扩展性,还具备良好的可维护性,非常适合用于构建高性能的网络应用程序。Netty 的核心实现基于 Reactor 模式,封装了底层复杂的事件驱动机制,使开发者能够专注于业务逻辑而无需处理底层通信细节。
        通过使用 Netty,开发者可以轻松实现 TCP、UDP、HTTP 等常见通信协议的支持,从而大大简化了底层通信的编程工作。无论是 TCP、UDP 这样的低层传输协议,还是 FTP、SMTP、HTTP 等应用层协议,Netty 都能提供统一且高效的编程模型,帮助开发者快速搭建稳定的网络服务端或客户端应用。

Netty 的目标

        Netty 的设计目标是帮助开发者快速而轻松地构建各类通信应用程序。它通过抽象底层复杂的网络通信细节,提供了清晰且统一的编程接口,使得网络程序的开发更加高效和简洁。
        在实现高性能通信方面,Netty 支持多种通信协议,能够处理包括 TCP、UDP 在内的传输层协议,以及 HTTP、FTP 等应用层协议。同时,Netty 基于异步非阻塞的通信机制,结合事件驱动的架构,有效提升了系统在高并发场景下的响应能力和吞吐量。
        为了实现灵活的通信控制,Netty 提供了一系列核心组件,包括用于数据传输的 Channel、用于处理通信逻辑的 Handler,以及用于执行 I/O 事件循环的 EventLoopGroup 等。这些组件相互协作,构成了 Netty 强大而灵活的通信框架核心。

Netty 实战案例 DiscardServer

什么是 DiscardServer?
一个丢弃消息的服务器:接收客户端消息后不做任何处理直接丢弃,相当于 Netty 的 “HelloWorld”

服务端程序 NettyDiscardServer

下面的代码很多地方是新知识,先大体了解代码功能,后续会进行详细讲解

服务端核心步骤
1. 创建反应器线程组(EventLoopGroup)
2. 配置 ServerBootstrap
3. 启动服务器
        bind().sync() 同步绑定。
        closeFuture().sync() 同步关闭监听。
        shutdownGracefully() 优雅关闭线程组。

// 定义一个Netty丢弃服务器类,功能是接收数据并直接丢弃
public class NettyDiscardServer {// 服务器监听的端口号private final int serverPort;// Netty的服务端启动类,用于配置和启动服务器ServerBootstrap b = new ServerBootstrap();// 构造函数,传入服务器端口public NettyDiscardServer(int port) {this.serverPort = port;}// 运行服务器的方法public void runServer() {// 创建boss事件循环组,处理连接请求,参数1表示使用1个线程EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);// 创建worker事件循环组,处理IO操作,不指定参数则使用默认线程数EventLoopGroup workerLoopGroup = new NioEventLoopGroup();try {// 配置服务器引导类b.group(bossLoopGroup, workerLoopGroup); // 设置事件循环组b.channel(NioServerSocketChannel.class); // 指定使用NIO传输通道b.localAddress(serverPort); // 设置服务器监听地址和端口b.option(ChannelOption.SO_KEEPALIVE, true); // 设置TCP保持连接选项// 配置子通道的处理器b.childHandler(new ChannelInitializer<SocketChannel>() {// 初始化通道时调用protected void initChannel(SocketChannel ch) {// 向管道添加自定义的处理器ch.pipeline().addLast(new NettyDiscardHandler());}});// 绑定服务器并同步等待绑定完成ChannelFuture channelFuture = b.bind().sync();// 打印服务器启动成功信息System.out.println("服务器启动成功,监听端口: " +channelFuture.channel().localAddress());// 获取通道关闭的FutureChannelFuture closeFuture = channelFuture.channel().closeFuture();// 同步等待服务器通道关闭closeFuture.sync();} catch (Exception e) {e.printStackTrace(); // 打印异常堆栈} finally {// 优雅关闭事件循环组workerLoopGroup.shutdownGracefully();bossLoopGroup.shutdownGracefully();}}// 主方法,程序入口public static void main(String[] args) {// 从配置类获取端口号int port = NettyDemoConfig.SOCKET_SERVER_PORT;// 创建服务器实例并运行new NettyDiscardServer(port).runServer();}
}

关键步骤:

1. 创建反应器线程组

EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);  // 负责接收客户端连接
EventLoopGroup workerLoopGroup = new NioEventLoopGroup(); // 负责处理 IO 读写

bossGroup:只负责 accept 新连接
workerGroup:负责处理每个连接的读写事件(比如数据收发)

2. 配置启动器 ServerBootstrap

b.group(bossLoopGroup, workerLoopGroup);
b.channel(NioServerSocketChannel.class); // 使用 NIO 的 ServerSocketChannel
b.localAddress(serverPort);              // 绑定监听端口
b.option(ChannelOption.SO_KEEPALIVE, true); // 保持长连接

channel():指定使用 NIO 网络模型
option():配置服务器通道参数,如是否保持连接

NioServerSocketChannel 是 Netty 中专门用于 NIO 模式下的 TCP 服务器端通道,对应底层的 ServerSocketChannel(Java NIO 中的 TCP 服务器套接字通道),专门处理 TCP 协议的连接请求

ChannelOption.SO_KEEPALIVE 是 TCP 协议的保活机制选项,用于设置是否开启 TCP 连接的保活功能。

当该选项设置为 true 时:

如果客户端和服务器长时间没有数据交互(默认通常是 2 小时左右,具体时间取决于系统配置),TCP 协议会自动发送探测数据包,检测对方是否还在线。
如果对方正常响应,说明连接仍然有效,继续保持连接;
如果多次探测无响应,会判定连接已失效,自动关闭连接,释放资源。

3. 设置子通道处理器

b.childHandler(new ChannelInitializer<SocketChannel>() {protected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new NettyDiscardHandler());}
});

每当有客户端连接成功,就会初始化它的通道(SocketChannel)
向 pipeline 添加处理器 NettyDiscardHandler

业务处理器 NettyDiscardHandler

NettyDiscardHandler 是服务端通道的自定义处理器,用于读取并丢弃客户端发送的数据。

// 自定义的Netty通道处理器,继承自ChannelInboundHandlerAdapter
public class NettyDiscardHandler extends ChannelInboundHandlerAdapter {// 当通道有数据可读时调用@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 将消息转换为ByteBuf类型ByteBuf in = (ByteBuf) msg;try {System.out.println("收到消息,丢弃如下:");// 循环读取ByteBuf中的可读字节while (in.isReadable()) {// 逐个字节读取并打印为字符System.out.print((char) in.readByte());}System.out.println(); // 打印换行} finally {// 释放消息资源,防止内存泄漏ReferenceCountUtil.release(msg);}}
}
步骤说明
channelRead()当通道收到客户端数据时触发
msg → ByteBuf将消息强制转换为 ByteBuf 类型
while (in.isReadable())遍历 ByteBuf 中所有可读字节
readByte()逐字节读取并打印内容
ReferenceCountUtil.release()释放 ByteBuf 引用,防止内存泄漏

为什么需要手动释放 ByteBuf?
Netty 使用引用计数机制管理内存,ByteBuf 在读取后必须显示释放,否则会导致内存泄漏。

ReferenceCountUtil.release(msg) 等价于调用 ((ByteBuf) msg).release(),但更加通用安全。

在 Netty 中,基于引用计数机制(ReferenceCounted 接口)管理的对象(如 ByteBuf),需要开发者显式调用释放方法来回收资源,这就是手动释放的含义 —— 不像 JVM 堆内存那样由垃圾回收器自动回收,而是需要代码中主动触发释放。

ReferenceCountUtil.release(msg) 的作用是:
1. 检查 msg 是否是引用计数对象(实现了 ReferenceCounted 接口,比如 ByteBuf)
2. 如果是,则调用其 release() 方法减少引用计数,当计数减为 0 时,对象会被释放(回收内存)
3. 如果不是引用计数对象,该方法会忽略操作,避免报错。

配置类 NettyDemoConfig
// Netty演示配置类
public class NettyDemoConfig {// 定义服务器端口常量public static final int SOCKET_SERVER_PORT = 12345;
}

如果没有客户端发送数据,只启动服务器无法验证效果。
所以可以使用之前的 NioDiscardClient 客户端,在下面链接有讲
详解 NIO Selector_java nio selectors 工作原理-CSDN博客
下面是完整的客户端代码,运行时需要先启动服务端,再启动客户端

public class NioDiscardClient {// 静态的 Logger 实例private static final Logger logger = LoggerFactory.getLogger(NioDiscardClient.class);public static void startClient() throws IOException {// 1. 配置服务器地址和端口InetSocketAddress address = new InetSocketAddress("127.0.0.1", 12345);// 2. 获取 SocketChannel 并连接到服务器SocketChannel socketChannel = SocketChannel.open(address);// 3. 设置为非阻塞模式socketChannel.configureBlocking(false);// 4. 等待连接建立完成while (!socketChannel.finishConnect()) {// 自旋等待连接完成}logger.info("客户端连接成功");// 5. 准备数据缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("hello world".getBytes());byteBuffer.flip();// 6. 将数据写入通道(发送到服务端)socketChannel.write(byteBuffer);// 7. 关闭输出流并断开连接socketChannel.shutdownOutput();socketChannel.close();}public static void main(String[] args) throws IOException {startClient();}
}

如何验证消息被丢弃?
启动客户端程序,向服务器发消息。
服务端打印接收到的内容,但不会返回任何响应。

回顾 Reactor 模式中的 IO 事件处理流程

单线程 Reactor 模式-CSDN博客
多线程 Reactor 模式-CSDN博客
如果不了解 Reactor 模式,可点击链接跳转学习

一个 IO 操作从内核产生到业务代码执行,会经历 4 个阶段:

阶段说明
第 1 步:注册IO 事件通过 Channel 注册到 Selector
第 2 步:查询Reactor(反应器)通过线程轮询 Selector,检查是否有事件就绪
第 3 步:分发如果发现事件,交给绑定的 Handler 处理
第 4 步:处理真正的 IO 读写逻辑或业务逻辑由 Handler 完成

第 1、2 步由 Java NIO 提供机制,第 3、4 步为 Reactor 核心。

Netty 中的 Channel

        在 Netty 中,Channel 是进行所有 IO 操作的核心载体。它是对 Java NIO 中 Channel 的封装和扩展,提供了更加灵活和统一的接口,使得网络通信编程变得更为简单高效。Channel 表示一个网络连接,既可以代表客户端的连接通道,也可以代表服务端的监听通道。
        针对不同的通信协议,例如 TCP、UDP 和 SCTP,Netty 提供了对应的 Channel 实现类。同时,为了支持不同的 IO 模型,Netty 为每种协议都提供了基于 NIO(非阻塞 IO)和 OIO(阻塞 IO)的两种实现版本。例如,针对 TCP 协议,Netty 提供了 NioSocketChannel 和 OioSocketChannel。
        通过这种方式,Netty 不仅屏蔽了底层 IO 实现的复杂性,还为用户提供了丰富且统一的通信编程模型。无论是同步通信还是异步通信,开发者都可以通过 Channel 接口轻松完成底层数据传输的操作。

常见 Channel 类型

协议类型Channel 实现
TCP 传输NioSocketChannel, OioSocketChannel
TCP 监听NioServerSocketChannel, OioServerSocketChannel
UDP 传输NioDatagramChannel, OioDatagramChannel
SCTP 传输NioSctpChannel, OioSctpChannel
SCTP 监听NioSctpServerChannel, OioSctpServerChannel

Netty 的 Channel 封装关系

        在 Netty 中,Channel 的实现并不是直接使用 Java NIO 的 Channel 接口,而是在其基础上进行了多层封装,以提供更高层次的抽象和更强的功能扩展。以常用的 NioSocketChannel 为例,它的继承结构遵循了一个清晰的层级体系:NioSocketChannel 继承自 AbstractNioByteChannel,而后者又继承自 AbstractNioChannel,再向上是 AbstractChannel,最终实现了最顶层的 Channel 接口。
        这种层层封装的设计,使得 Netty 能够在不同层级中分离出通用的逻辑与具体的实现细节,从而提升代码的复用性与扩展性。同时,尽管 Netty 封装了自己的 Channel 体系,但其最底层依然是依赖于 Java NIO 中的 SelectableChannel,也就是说,Netty 的所有 IO 操作最终都会落地到 NIO 提供的底层通道上执行。因此,Netty 在提升开发体验的同时,也保留了 NIO 高性能的核心优势。

顶层接口(Channel):定义通用行为,与具体实现无关
抽象类(AbstractChannel 等):封装通用逻辑,沉淀复用代码
具体实现类(NioSocketChannel 等):专注于特定协议和 IO 模型的细节

SelectableChannel 是 Java NIO 中的一个抽象类,是所有支持多路复用(Selector)通道(Channel)的父类。它的核心作用是允许通道被注册到 Selector 上,通过 Selector 实现对多个通道的 IO 事件(读就绪、写就绪、连接就绪等)进行统一监听和管理。

Netty 中的 Reactor

        在 Netty 中,Reactor 模式的核心组件之一是 NioEventLoop,负责事件轮询与处理调度。NioEventLoop 是 Netty 对 Java NIO 中事件驱动机制的封装,其本质是一个反应器线程,用于不断轮询底层的 Selector,监听是否有 IO 事件发生,并在事件就绪后将其分发给对应的处理器(Handler)进行处理。
        每一个 NioEventLoop 都拥有一个独立的线程,并且绑定了一个 Selector 实例。它们之间形成一一对应的关系,保证事件处理的线程安全性和高效性。此外,Netty 设计中允许多个 Channel 被注册到同一个 NioEventLoop 上,从而实现一对多的高效事件管理机制。这种设计不仅充分利用了线程资源,还有效支持了高并发场景下的网络通信。

NioEventLoop 的继承结构:

        在 Netty 的架构中,NioEventLoop 是实现事件驱动模型的核心类之一,它的继承结构体现了 Netty 对事件处理流程的高度抽象与组织。具体来说,NioEventLoop 继承自 SingleThreadEventLoop,而 SingleThreadEventLoop 又继承自 SingleThreadEventExecutor,最终实现了顶层的 Executor 接口。
        这一层层的继承关系体现了职责的逐步细化:从通用的任务执行器(Executor),到专用于单线程执行的事件循环体(SingleThreadEventExecutor),再到真正绑定了 IO 事件处理能力的 NioEventLoop。通过这样的设计,Netty 将事件处理的通用性与专业性有机结合,使得每一个 EventLoop 既具备强大的调度能力,又能专注于单线程下的高效执行。
        在实现细节上,NioEventLoop 拥有两个至关重要的成员属性:其一是 Thread,即事件处理线程;其二是 Selector,用于监听和轮询注册的 IO 事件。这两个核心组件配合使用,使得 NioEventLoop 成为支撑 Netty 高性能异步 IO 的关键支点。

EventLoop 与 Channel 的关系

一个 EventLoop 可以管理成千上万个 Channel。
属于一对多的绑定结构。

Netty 中的 Handler

IO 事件类型:

类型对应 SelectionKey
读就绪OP_READ
写就绪OP_WRITE
连接完成OP_CONNECT
接收连接OP_ACCEPT

Handler 分类:

类型说明
ChannelInboundHandler入站事件处理器(如读数据)
ChannelOutboundHandler出站事件处理器(如写数据)

        在 Netty 中,Handler 是承载业务逻辑处理的核心组件。它用于响应和处理各种 IO 事件,是连接数据与业务处理之间的桥梁。根据事件的方向不同,Handler 被分为两类:入站处理器和出站处理器。
        ChannelInboundHandler 是处理入站事件的处理器,负责响应例如客户端连接建立、数据读取等事件,典型方法如 channelRead()、channelActive()。而 ChannelOutboundHandler 则用于处理出站事件,主要负责将数据写出到网络或进行编码转换等操作,典型方法如 write()、flush()。
        每个 Channel 在创建时,都会绑定一个 ChannelPipeline,这个 Pipeline 中可以包含多个 Handler。通过 Pipeline 的链式结构,Netty 能够灵活地控制事件在多个 Handler 之间的流转,从而实现复杂、可扩展的网络业务处理逻辑。

Netty 中的 Pipeline

        在 Netty 中,ChannelPipeline 是每个 Channel 所独有的组件,它是一个双向链表结构,专门用于管理和组织多个 Handler 的执行顺序。可以理解为,Pipeline 就是一条事件处理的通道,所有与该 Channel 相关的 IO 事件都会按照这条通道中 Handler 的顺序依次传递与处理。
        通过 ChannelPipeline,Netty 实现了对事件处理器(Handler)的链式管理,不仅支持事件的入站处理(Inbound),也支持事件的出站处理(Outbound)。事件在 Pipeline 中的传递方向由事件类型决定:入站事件会按照添加顺序正向传递,出站事件则会反向传递。
        这种设计带来了高度的灵活性,使开发者能够按需在不同位置添加、替换或删除 Handler,从而精细控制事件处理流程,实现清晰、模块化的通信逻辑。简而言之,ChannelPipeline 是 Netty 中连接 Channel 与业务处理器之间的桥梁,也是事件流动和处理的主通道。

普通 Reactor 模式是事件 → 单个处理器(内部包含所有步骤),而 Netty 是事件 → 流水线(多个独立 Handler 按顺序协作)

入站/出站执行顺序
1. IO 事件进入 Pipeline。
2. 沿着 Handler 链处理:
        入站事件按前向顺序触发 InboundHandler。
        出站事件按后向顺序触发 OutboundHandler。


尚未完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916873.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916873.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于“LoggerFactory is not a Logback LoggerContext but Logback is on ......“的解决方案

​ ✨重磅&#xff01;盹猫的个人小站正式上线啦&#xff5e;诚邀各位技术大佬前来探秘&#xff01;✨ 这里有&#xff1a; 硬核技术干货&#xff1a;编程技巧、开发经验、踩坑指南&#xff0c;带你解锁技术新姿势&#xff01;趣味开发日常&#xff1a;代码背后的脑洞故事、工具…

2025年6月电子学会青少年软件编程(C语言)等级考试试卷(三级)

答案和更多内容请查看网站&#xff1a;【试卷中心 -----> 电子学会 ----> C/C ---->三级】 网站链接 青少年软件编程历年真题模拟题实时更新 编程题 第 1 题 打印城门 题目描述 给定一个正整数 n&#xff0c;输出如下的星号城门。具体格式请见样例。 输入格…

跨平台直播美颜SDK开发指南:兼顾性能与美型效果的最佳实践

面对iOS、Android乃至Web等多端应用需求&#xff0c;如何开发一款真正跨平台、兼顾性能与美型效果的美颜SDK&#xff0c;成为众多开发团队和产品经理的一道必答题。 今天笔者这篇文章&#xff0c;就从架构设计、性能优化、视觉效果调校三个关键维度&#xff0c;带你深入解析跨平…

2025数字藏品安全保卫战:高防CDN如何成为NFT应用的“隐形护甲”?

副标题&#xff1a; 从DDoS防御到全球加速&#xff0c;拆解数字资产平台的生死防线&#x1f310; 引言&#xff1a;当数字藏品成为黑客的“头号靶场”2025年全球数字藏品市场突破$1000亿&#xff0c;但安全事件同步激增230%——某头部NFT平台因3.2Tbps DDoS攻击瘫痪&#xff0c…

linux 执行sh脚本,提示$‘\r‘: command not found

1、在Linux下执行某个脚本文件却提示$\r: command not found&#xff0c;如下图:2、错误原因:a、 Windows 风格的换行符&#xff1a;Windows 系统使用 \r\n 作为行结束符&#xff0c;而 Linux 和 Unix 系统使用 \n。当你从 Windows 环境中复制文本到 Linux 环境时&#xff0c;可…

使用HaiSnap做了一款取件码App(一键生成)

你是否怀揣着奇思妙想&#xff0c;却因不懂代码而对开发应用望而却步&#xff1f;现在&#xff0c;有一个神奇AI Agent&#xff08;响指HaiSnap&#xff09;&#xff0c;一个响指就能实现&#xff0c;你说神奇不&#xff1f;只需要一句话就可以生成你想要的应用&#xff01;让你…

容器与虚拟机的本质差异:从资源隔离到网络存储机制

目录 专栏介绍 作者与平台 您将学到什么&#xff1f; 学习特色 容器与虚拟机的本质差异&#xff1a;从资源隔离到网络存储机制 一、容器与虚拟机的本质区别 1.1 资源抽象层次差异 1.2 资源消耗与性能对比 1.3 隔离性深度差异 二、容器网络基础架构 2.1 Docker网络模型…

ros2 launch文件编写详解

一个完整的简单的launch文件配置过程1.编写launch文件2.配置package.xml3.配置setup.py&#xff08;python包&#xff09;4.配置CMakeList(C包)5.编译运行# 在 ROS 2 的 Python 启动文件中&#xff0c;这些导入语句用于引入各类启动模块&#xff0c;以构建和配置节点启动流程 f…

QT中QTableView+Model+Delegate实现一个demo

一、概述功能: 实现一个查询学生信息的表格&#xff0c;有学号、性别、年龄、班级和分数共5列&#xff0c;针对最后一列分数实现委托代理&#xff0c;要求能编辑和查看该分数列。QTableView实现视图展示uiModel负责数据的构造Delegate是委托&#xff0c;可针对某列数据做自定义…

用latex+vscode写论文

文章目录 前言 一、下载texlive安装包 二、安装texlive 1.安装 2.配置环境变量 3.检查是否安装成功 三、安装vscode 四、vscode中安装latex workshop插件 五、创建latex文档 六、撰写+编译+预览 七、latex workshop常用设置 1.打开设置页面 2.设置自动保存代码 3.设置自动编译代…

监测预警系统:让园区更高效、更安全、更智能

随着城市化进程的加快和产业集聚效应的凸显&#xff0c;园区作为经济发展的重要载体&#xff0c;其规模不断扩大&#xff0c;功能日益复杂。在这一背景下&#xff0c;传统的园区管理模式已难以满足现代园区高效、安全、智能的运营需求。园区监测预警系统作为一种集成了物联网、…

分享一个AutoOff定时动作软件

我们平时在使用电脑的时候有很多需求的功能&#xff0c;比如定时打开程序、定时关闭程序、定时休眠、定时关机等等。如果你也有这样的需求&#xff0c;那么就需要今天这款软件。AutoOff定时动作软件AutoOff这个软件是一款定时的软件&#xff0c;软件大小只有1.1M&#xff0c;而…

RPA软件推荐:提升企业自动化效率

在数字化转型浪潮中&#xff0c;机器人流程自动化&#xff08;RPA&#xff09;已成为企业降本增效的核心工具。它通过模拟人类操作&#xff0c;自动化重复性任务&#xff0c;如数据录入、报表生成和系统集成&#xff0c;显著提升运营效率。面对众多RPA软件&#xff0c;如何选择…

【Qt】QTime::toString(“hh:mm:ss.zzz“) 显示乱码的原因与解决方案

在使用 Qt 编写计时器程序时&#xff0c;我遇到一个很奇怪的问题&#xff1a;使用 QTime::toString("hh:mm:ss.zzz") 格式化时间后&#xff0c;显示出来的是一串乱码&#xff0c;如下所示&#xff1a;本来应该是&#xff1a;但却显示了一堆“〇”或奇怪的符号。问题表…

MSVC编译KDChart过程

MSVC编译KDChart过程 一、下载KDChart源文件 GitHub源文件 查看文件夹里的INSTALL.txt&#xff0c;可以看到需要CMake编译。 中文版 以下是使用 CMake 构建系统安装 KD Chart 的说明。 需要 CMake 3.3 或更高版本。Qt 版本支持&#xff1a;* KD Chart 2.5.0 及以下版本需要…

AI Agent管理后台原型设计全拆解(附3套可复用素材)

最近在做AI方向的产品&#xff0c;越来越能感受到“智能体”这个概念正在从技术圈走向应用层。无论是内部探索项目&#xff0c;还是外部合作需求&#xff0c;很多场景都会提到Agent、助手、知识库这些关键词。我们经常讨论如何打造一个有用的AI Agent&#xff0c;但实际上&…

ABP VNext + Elastic APM:微服务性能监控

ABP VNext Elastic APM&#xff1a;微服务性能监控 &#x1f680; &#x1f4da;目录ABP VNext Elastic APM&#xff1a;微服务性能监控 &#x1f680;一、引言 ✨架构全景图 &#x1f3d7;️二、环境与依赖 &#x1f4e6;三、APM 服务器与 Kibana 快速部署 &#x1f433;Doc…

单片机学习笔记.AD/DA(略含有SPI,用的是普中开发板上的XPT2046芯片)

AD/DA基础知识 硬件电路&#xff1a; 模电运放知识回顾&#xff1a; 虚短&#xff08;Virtual Short&#xff09;定义&#xff1a;运放同相输入端&#xff08;&#xff09;和反相输入端&#xff08;-&#xff09;的电位近似相等&#xff0c;即V V-&#xff0c;仿佛两个输入端短…

避坑指南:VMware安装CentOS常见错误及完美解决方案

1. 引言 虚拟机允许在同一台机子上有不同的操作系统&#xff0c;还可以用于搭建实验环境、软件测试和兼容性测试等。我是主攻大数据方向的会用到Linux操作系统&#xff0c;所以虚拟机对我来说是很有必要的。我把之前的笔记和安装包整理了一下&#xff0c;就有了现在这个教程。…

Python爬虫04_Requests豆瓣电影爬取

一、 爬取豆瓣电影排行榜数据 import requests import json url ‘https://movie.douban.com/j/chart/top_list’ param { ‘type’:‘24’, ‘interval_id’:‘100:90’, ‘action’:‘’, ‘start’:‘0’, #从库中的第几部电影去取 ‘limit’:‘20’, } headers { ‘Use…