分布式系统通信解决方案:Netty 与 Protobuf 高效应用
分布式系统通信解决方案:Netty 与 Protobuf 高效应用
一、引言
在现代网络编程中,数据的编解码是系统设计的一个核心问题,特别是在高并发和低延迟的应用场景中,如何高效地序列化和传输数据对于系统的性能至关重要。随着分布式系统和微服务架构的广泛应用,跨平台、高效的序列化方案变得愈加重要。
Protocol Buffers(简称 Protobuf)是 Google 开发的一个高效的序列化工具。它能够将结构化数据序列化为紧凑的二进制格式,具有以下显著优势:
- 高效紧凑:Protobuf 使用二进制格式,比传统的 JSON 和 XML 更紧凑,占用更少的存储空间,传输速度更快,适用于高负载、高频繁的网络通信。
- 跨语言支持:Protobuf 支持多种编程语言,包括 Java、Python、C++、Go 等,使得它在异构系统之间传输数据时具有极好的兼容性。
- 灵活性强:Protobuf 支持复杂的数据结构,如嵌套对象、列表以及可选和重复字段,使得开发者可以灵活定义数据格式,满足不同的业务需求。
随着 Netty 成为构建高性能网络服务的标准框架之一,如何将 Protobuf 与 Netty 高效结合,利用它们的优势实现快速、高效的网络通信,成为了很多开发者关心的课题。本文将深入探讨如何在 Netty 中使用 Protobuf 实现高效的数据编解码,并通过具体的代码示例演示其应用。
二、Protobuf 的基础知识
2.1 什么是 Protobuf?
Protobuf 是一种语言无关、平台无关的数据序列化协议。它通过定义 .proto
文件来描述数据结构,然后使用 Protobuf 编译器(protoc
)生成特定语言的代码来实现数据的序列化与反序列化操作。
Protobuf 提供了一种高效、紧凑的方式来存储和交换数据,与传统的 JSON 和 XML 相比,它更加节省带宽和存储空间,特别适用于高并发、低延迟的网络通信场景。
2.2 Protobuf 的优点
- 效率高:
Protobuf 使用二进制格式序列化数据,比 JSON 和 XML 格式更紧凑。解析速度也更快,在大规模的数据传输和存储中具有明显的性能优势。 - 跨平台支持:
Protobuf 支持多种编程语言,包括 Java、Python、C++、Go 等,能够在不同平台和技术栈之间无缝传输数据。这使得它在异构系统的集成中非常有用。 - 结构清晰:
.proto
文件提供了一种简单、清晰的数据描述方式,所有的数据结构都可以在.proto
文件中明确地定义。开发者只需关注业务逻辑,而不必过多关心底层的序列化和反序列化细节。 - 易扩展性:
Protobuf 支持向现有消息结构中添加新字段而不影响旧的消息解析,这为系统的演进和扩展提供了极大的灵活性。
2.3 Protobuf 的基本工作流程
Protobuf 的使用流程非常简单:
- 定义
.proto
文件:描述数据结构(如消息类型、字段名称和数据类型)。 - 生成代码:使用 Protobuf 编译器(
protoc
)将.proto
文件编译成对应语言的代码。 - 序列化与反序列化:在应用程序中,使用生成的代码进行数据的序列化和反序列化。
这种工作流使得 Protobuf 成为在分布式系统、微服务架构和跨平台通信中,处理数据交换的理想选择。
三、在 Netty 中使用 Protobuf
Netty 提供了对 Protobuf 的原生支持,主要通过以下编解码器实现:
- ProtobufEncoder: 将消息序列化为二进制数据。
- ProtobufDecoder: 将二进制数据反序列化为 Protobuf 对象。
此外,为了解决 TCP 拆包与黏包问题,Netty 中通常配合使用 LengthFieldBasedFrameDecoder
和 LengthFieldPrepender
。
四、Protobuf 在 Netty 中的基础应用
下面通过一个完整的示例展示如何在 Netty 中结合 Protobuf 实现客户端与服务端的数据传输。
1. Protobuf 工具与 Java 8 的兼容性
Protobuf 编译器(protoc
) 生成的代码与 Java 运行时兼容。由于 Java 8 是较旧的版本,需要确保以下两点:
- 选择的 Protobuf 编译器生成的代码与 Java 8 的字节码兼容。
- Protobuf 的运行时库(
protobuf-java
)版本与生成代码保持一致,并支持 Java 8。
2. 工具包版本推荐
对于 Java 8,可以使用以下 Protobuf 版本:
- Protobuf 编译器(protoc): 推荐使用 3.19.x 或更早的版本。这些版本生成的代码默认是兼容 Java 8 的。
- 运行时库(protobuf-java): 确保与 Protobuf 编译器版本一致,例如 3.19.x。
Protobuf 3.20.x 及更高版本生成的代码可能默认使用 Java 11 特性(如模块化支持),因此对于 Java 8 不再完全兼容。
3. 下载 Protobuf 编译器
-
下载链接:
- 从 Protobuf Releases 页面选择版本(推荐 3.19.x 或更早版本)。
- Protobuf 3.19.4 版本
- 下载与操作系统对应的预编译二进制文件(
protoc-3.x.x-[platform].zip
)。 - 解压后将
protoc
可执行文件的路径加入系统环境变量。
新建项目
引入依赖
在 Maven 项目中添加以下依赖:
<dependencies><!-- Netty 核心依赖 --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.97.Final</version></dependency><!-- Netty Protobuf 编解码支持 --><dependency><groupId>io.netty</groupId><artifactId>netty-codec-protobuf</artifactId><version>4.1.97.Final</version></dependency><!-- Protobuf 运行时库 --><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.19.4</version> <!-- 与 Protobuf 编译器版本匹配 --></dependency><!-- Protobuf 编译插件(如果需要通过 Maven 编译 .proto 文件) --><dependency><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version></dependency>
</dependencies>
4.2 定义 Protobuf 消息
在使用 Protocol Buffers(Protobuf)时,所有数据的定义都需要通过 .proto
文件进行描述。Protobuf 是一种语言无关的、平台无关的序列化数据结构的方式,能够在不同编程语言之间传输数据。在 Protobuf 中,数据通过消息(message
)类型定义,每个字段都有类型和唯一的标识符。
什么是 .proto
文件?
.proto
文件是 Protobuf 的定义文件,其中定义了消息类型、字段的名称、数据类型以及字段的编号等信息。它是 Protobuf 数据序列化和反序列化的基础。每个消息类型可以包含多个字段,每个字段有一个编号,Protobuf 会根据这个编号在序列化时决定字段的顺序。
在 .proto
文件中,你需要遵循一定的语法规则来定义数据结构。Protobuf 支持多种数据类型,包括基本类型(如 int32
、string
、bool
等)以及复合类型(如 message
、enum
和 repeated
)。
Protobuf 文件示例
以下是拆分后的两个 .proto
文件:
inventory_request.proto
syntax = "proto3";package com.example.protobuf;
option java_outer_classname = "InventoryRequestModel";
// InventoryRequest 消息定义
message InventoryRequest {string product_id = 1; // 产品 IDint32 quantity = 2; // 请求的数量string operation = 3; // 操作类型:add 或 remove
}
在此文件中,我们定义了一个名为 InventoryRequest
的消息类型:
product_id
:表示产品的唯一标识符,类型为string
。quantity
:表示请求的数量,类型为int32
。operation
:表示操作类型,可能的值有add
或remove
,类型为string
。
inventory_response.proto
syntax = "proto3";package com.example.protobuf;
option java_outer_classname = "InventoryResponseModel";
// InventoryResponse 消息定义
message InventoryResponse {string product_id = 1; // 产品 IDbool success = 2; // 操作是否成功string message = 3; // 响应消息
}
在此文件中,我们定义了一个名为 InventoryResponse
的消息类型:
product_id
:表示产品的唯一标识符,类型为string
。success
:表示操作是否成功,类型为bool
。message
:表示响应消息的详细信息,类型为string
。
如何生成 Java 类
通过运行 protoc
编译器,我们可以根据 .proto
文件生成对应的 Java 类。这些类将用于 Java 应用程序中与 Protobuf 消息进行交互。
使用以下命令生成对应的 Java 类:
protoc -I=D:\code\java\myproject\netty-003\src\main\java --java_out=D:\code\java\myproject\netty-003\src\main\java\ D:\code\java\myproject\netty-003\src\main\java\com\example\protobuf\*.proto
此命令做了以下几件事:
-I=D:\code\java\myproject\netty-003\src\main\java
:指定.proto
文件的根目录。--java_out=D:\code\java\myproject\netty-003\src\main\java\
:指定生成的 Java 类的输出目录。D:\code\java\myproject\netty-003\src\main\java\com\example\protobuf\*.proto
:指定要编译的.proto
文件路径,可以使用通配符*.proto
来一次性编译多个.proto
文件。
Protobuf 官方文档
更多关于 Protobuf 文件语法、类型、字段规则等内容,可以参考 Protobuf 的官方文档:
- Protobuf 官方文档地址:
https://developers.google.com/protocol-buffers
在文档中,你可以深入了解如何定义不同的数据类型、字段规则以及 Protobuf 的高级用法。
4.3 服务端实现
服务端处理逻辑(Handler):
package com.example.protobuf;
import com.example.protobuf.InventoryRequestModel.InventoryRequest;
import com.example.protobuf.InventoryResponseModel.InventoryResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;public class ProtobufServerHandler extends SimpleChannelInboundHandler<InventoryRequest> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, InventoryRequest request) {System.out.println("收到客户端请求:" + request);// 模拟库存处理逻辑boolean success = "add".equals(request.getOperation()) || "remove".equals(request.getOperation());String message = success ? "操作成功!" : "操作失败,未知操作类型:" + request.getOperation();// 构造响应对象InventoryResponse response = InventoryResponse.newBuilder().setProductId(request.getProductId()).setSuccess(success).setMessage(message).build();// 发送响应ctx.writeAndFlush(response);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}
服务端启动类:
package com.example.protobuf;import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;public class ProtobufServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));ch.pipeline().addLast(new ProtobufDecoder(InventoryRequestModel.InventoryRequest.getDefaultInstance()));ch.pipeline().addLast(new LengthFieldPrepender(4));ch.pipeline().addLast(new ProtobufEncoder());ch.pipeline().addLast(new ProtobufServerHandler());}});ChannelFuture future = bootstrap.bind(8080).sync();System.out.println("服务端已启动,端口:8080");future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
4.4 客户端实现
客户端处理逻辑(Handler):
package com.example.protobuf;import io.netty.channel.ChannelHandlerContext;
import com.example.protobuf.InventoryRequestModel.InventoryRequest;
import com.example.protobuf.InventoryResponseModel.InventoryResponse;
import io.netty.channel.SimpleChannelInboundHandler;import java.nio.charset.StandardCharsets;public class ProtobufClientHandler extends SimpleChannelInboundHandler<InventoryResponse> {@Overridepublic void channelActive(ChannelHandlerContext ctx) {InventoryRequest request = InventoryRequest.newBuilder().setProductId("P12345").setQuantity(10).setOperation("add").build();ctx.writeAndFlush(request);System.out.println("客户端已发送请求:" + request);}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, InventoryResponse response) {System.out.println("收到服务端响应:" + response);
// System.out.println(new String(response.getMessage().getBytes(StandardCharsets.UTF_8)));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}
客户端启动类:
package com.example.protobuf;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;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;public class ProtobufClient {public static void main(String[] args) throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));ch.pipeline().addLast(new ProtobufDecoder(InventoryResponseModel.InventoryResponse.getDefaultInstance()));ch.pipeline().addLast(new LengthFieldPrepender(4));ch.pipeline().addLast(new ProtobufEncoder());ch.pipeline().addLast(new ProtobufClientHandler());}});ChannelFuture future = bootstrap.connect("localhost", 8080).sync();future.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}
}
五、总结
在本篇文章中,我们深入探讨了如何在 Netty 中结合 Protobuf 实现高效的数据编解码。通过详细的代码示例,我们展示了如何利用 Protobuf 轻松进行数据序列化与反序列化,并在 Netty 的高性能网络通信中应用。
1. Protobuf 的优势与应用场景
Protobuf 作为一种高效的二进制序列化协议,具有以下显著优势:
- 高效紧凑:通过紧凑的二进制格式,Protobuf 可以显著减少带宽消耗和存储需求,在高负载、高频繁的网络通信中表现尤为突出。
- 跨平台支持:Protobuf 支持多种编程语言,能够在不同语言和平台之间无缝传输数据,特别适合分布式系统和微服务架构中异构系统的数据交换。
- 灵活扩展:Protobuf 提供灵活的结构扩展机制,能够在不破坏现有系统的情况下,向消息中添加新的字段,保证系统的平稳演化。
这些优势使得 Protobuf 成为高效数据传输的首选,特别是在大规模、高并发、低延迟的应用场景中,如分布式系统、实时数据传输、微服务架构等。
2. Netty 与 Protobuf 的结合
Netty 是一个高性能的网络框架,适用于处理大量并发连接和高效数据传输。通过将 Netty 与 Protobuf 结合,开发者可以在保证高性能的同时,还能有效地进行数据序列化和反序列化。结合 ProtobufEncoder 和 ProtobufDecoder,数据的传输效率大大提升,特别是当需要传输大量结构化数据时。
Netty 提供了原生支持,简化了 Protobuf 的使用,只需通过简单的编码解码器配置,就可以实现 Protobuf 消息的高效传输。此外,Netty 的 LengthFieldBasedFrameDecoder
和 LengthFieldPrepender
解码器,帮助我们解决了 TCP 拆包和 黏包的问题,确保消息完整性和传输的可靠性。
3. 实践中的建议
在实际开发中,结合 Netty 和 Protobuf 的使用,可以进一步优化网络服务的性能:
- 性能调优:根据业务需求,可以调整 Protobuf 的编解码策略,例如通过压缩数据来减少带宽占用;在高并发场景下,可以使用 Protobuf 的压缩选项来进一步提高传输效率。
- 多种协议的结合:除了 Protobuf,Netty 还支持其他协议(如 JSON、XML、Thrift 等),你可以根据不同的应用场景,选择适合的数据格式进行组合。
- 错误处理与安全:在处理实际应用时,务必考虑错误处理和安全性。例如,使用适当的验证机制来防止恶意数据注入,并确保网络连接的安全性(如使用 SSL/TLS 加密)。
4. 高效的跨平台通信
通过本篇博客,你已经学会了如何使用 Protobuf 和 Netty 实现高效、可扩展的跨平台网络通信。无论是在微服务架构中,还是在大规模的分布式系统中,利用这两者的结合,都能够实现高效的消息传递,保证系统的高并发和低延迟特性。
5. 后续学习与扩展
如果你希望进一步优化系统的性能,或在更复杂的场景中使用 Netty 和 Protobuf,可以从以下几个方向进行学习和扩展:
- Protobuf 高级特性:深入学习 Protobuf 的更多高级特性,如自定义序列化和反序列化逻辑、扩展机制等。
- Netty 高级用法:学习 Netty 更高级的特性,例如自定义协议处理、事件驱动模型的优化、流量控制等。
- 性能优化:根据实际需求,结合负载均衡、数据压缩和缓存机制等技术,进一步提高系统的吞吐量和响应速度。
通过不断实践和优化,你将能够构建更加高效、灵活和可扩展的网络服务。
相关文章:

分布式系统通信解决方案:Netty 与 Protobuf 高效应用
分布式系统通信解决方案:Netty 与 Protobuf 高效应用 一、引言 在现代网络编程中,数据的编解码是系统设计的一个核心问题,特别是在高并发和低延迟的应用场景中,如何高效地序列化和传输数据对于系统的性能至关重要。随着分布式系…...

计算机网络 (54)系统安全:防火墙与入侵检测
前言 计算机网络系统安全是确保网络通信和数据不受未经授权访问、泄露、破坏或篡改的关键。防火墙和入侵检测系统(IDS)是维护网络系统安全的两大核心组件。 一、防火墙 定义与功能 防火墙是一种用来加强网络之间访问控制的特殊网络互联设备,它…...
stack底层实现细节
一、stack 和 queue 在 STL 中 stack 和 queue 已经不算是容器了,而是容器适配器,适配器模式也是常用的模式之一,体现在 stack 和 queue 中就是他们两个的实现不是单独写的,而是复用了前面合适的优秀的STL 容器的代码而实现的具有…...

工业相机 SDK 二次开发-Halcon 插件
本文介绍了 Halcon 连接相机时插件的使用。通过本套插件可连接海康 的工业相机。 一. 环境配置 1. 拷贝动态库 在 用 户 安 装 MVS 目 录 下 按 照 如 下 路 径 Development\ThirdPartyPlatformAdapter 找到目录为 HalconHDevelop 的文 件夹,根据 Halcon 版本找到对…...

map和set的使用(一)详解
文章目录 序列式容器和关联式容器map和set的介绍set构造和迭代器遍历和insertfinderaseswapclearcountlower_bound和upper_boundmultiset和set的对比 set的二个题目题目解析算法原理代码介绍一个找差集的算法同步算法题目解析算法原理代码 map构造遍历initiaizer_list 序列式容…...

ARP 表、MAC 表、路由表、跨网段 ARP
文章目录 一、ARP 表1、PC2、路由器 - AR22203、交换机 - S57004、什么样的设备会有 ARP 表? 二、MAC 表什么样的设备会有 MAC 表? 三、路由表什么样的设备会有路由表? 四、抓取跨网段 ARP 包 所谓 “透明” 就是指不用做任何配置 一、ARP 表…...
37.构造回文字符串问题|Marscode AI刷题
1.题目 问题描述 小C手中有一个由小写字母组成的字符串 s。她希望构造另一个字符串 t,并且这个字符串需要满足以下几个条件: t 由小写字母组成,且长度与 s 相同。t 是回文字符串,即从左到右与从右到左读取相同。t 的字典序要小…...

ssm-mybatisPlus学习笔记
注意!mybatisPlus只能够进行单表操作,其他的仍需要mybatis 1.快速入门 编写启动类 MapperScan("com.atguigu.mapper") SpringBootApplication public class MainApplication {public static void main(String[] args) {SpringApplication.r…...
【算法学习笔记】35:扩展欧几里得算法求解线性同余方程
线性同余方程问题 线程同余方程问题是指 a x ≡ b ( m o d m ) ax \equiv b~(mod~m) ax≡b (mod m),给定 a a a、 b b b和 m m m,找到一个整数 x x x使得该方程成立,即使得 a x m o d m b ax~mod~mb ax mod mb,随便返回任何一个…...

线性规划:机器学习中的优化利器
一、线性规划的基本概念 线性规划(Linear Programming, LP)是运筹学中数学规划的一个重要分支,用于在一组线性不等式的约束条件下,找到线性目标函数的最大值或最小值。其问题可以表述为: 在一组线性约束条件 s.t.&am…...
Ubuntu开发中的问题
1.退出anaconda指令:conda deactivate 2.Linux系列:一打开终端就默认进入conda的base环境,取消方法 在终端输入conda config --show,会显示所有的配置信息,然后利用conda config --set来修改此配置: conda config --se…...
MAC 地址转换为标准大写格式
// ConvertToStandardMac 将 MAC 地址转换为标准格式,确保每个字节都是两位,并且字母是大写的 func ConvertToStandardMac(mac string) (string, error) { // 分割 MAC 地址的每一部分 parts : strings.Split(mac, ":") // 确保每部分是两…...

使用插件SlideVerify实现滑块验证
作者gitee地址:https://gitee.com/monoplasty/vue-monoplasty-slide-verify 使用步骤: 1、安装插件 npm install --save vue-monoplasty-slide-verify 2、在main.js中进行配置 import SlideVerify from vue-monoplasty-slide-verify; Vue.use(SlideV…...
深入探索 Nginx 的高级用法:解锁 Web 服务器的强大潜能
在当下互联网技术飞速发展的浪潮中,Nginx 凭借其轻量级、高性能的特性,在 Web 服务器和反向代理服务器领域脱颖而出,成为众多开发者和运维工程师的得力工具。它不仅能高效处理静态资源,在负载均衡、反向代理等方面也表现出色。然而…...
(01)搭建开发环境
1.安装虚拟机软件 VMware Workstation Pro 17 2.虚拟机安装ubuntu20.4系统 3.安装VMtools工具 4.安装vim编辑器 sudo apt install vim 4.安装SSH服务 选择下载源为:http://mirrors.aliyun.com/ubuntu在线安装:sudo apt-get install openssh-serv…...
Win11桌面右键刷新选项在二级界面的修正方法
win10已经被弃用了,现在的win11在桌面右键时,“刷新”按钮在二级界面。除此以外,在资源管理器中浏览文件的时候,很多其他选项也都被放在了二级界面,非常不方便。接下来介绍一个把右键菜单栏中的所有选项都显示在一级界…...

配电室防静电地板通常用哪种
配电室是指带有低压负荷的室内配电场所,包含变压器、配电柜、开关设备等,主要为低压用户配送电能。为防止设备故障、避免火灾爆炸、保护人员安全等均会安装防静电地板。那么配电室防静电地板通常用哪种? 一、全钢防静电地板 1. 全钢三聚氰胺…...

【重庆市乡镇界】面图层shp格式arcgis数据乡镇名称和编码wgs84坐标无偏移内容测评
标题中的“最新重庆市乡镇界面图层shp格式arcgis数据乡镇名称和编码wgs84坐标无偏移最新”指的是一个地理信息系统(GIS)的数据集,特别设计用于ArcGIS软件。这个数据集包含了重庆市所有乡镇的边界信息,以Shapefile(.shp…...

68,[8] BUUCTF WEB [RoarCTF 2019]Simple Upload(未写完)
<?php // 声明命名空间,遵循 PSR-4 自动加载规范,命名空间为 Home\Controller namespace Home\Controller;// 导入 Think\Controller 类,以便扩展该类 use Think\Controller;// 定义 IndexController 类,继承自 Think\Control…...

Windows电脑桌面记录日程安排的提醒软件
在快节奏的现代生活中,工作效率成为了衡量个人能力的重要标准之一。然而,日常办公中常常会遇到各种琐事和任务,如果没有合理安排日程,很容易陷入混乱,导致效率低下。因此,做好日程安排对于日常工作至关重要…...

Arduino学习-跑马灯
1、效果 2、代码 /**** 2025-5-30 跑马灯的小程序 */ //时间间隔 int intervaltime200; //初始化函数 void setup() {// put your setup code here, to run once://设置第3-第7个引脚为输出模式for(int i3;i<8;i){pinMode(i,OUTPUT);} }//循环执行 void loop() {// put you…...

ASP.NET Core SignalR的基本使用
文章目录 前言一、SignalR是什么?在 ASP.NET Core 中的关键特性:SignalR 工作原理简图: 二、使用步骤1.创建ASP.NET Core web Api 项目2.添加 SignalR 包3.创建 SignalR Hub4.配置服务与中间件5.创建控制器(模拟服务器向客户端发送消息)6.创建…...
GitHub 趋势日报 (2025年05月28日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 2379 agenticSeek 1521 computer-science 841 n8n 577 langflow 351 qlib 282 skt…...

声纹技术体系:从理论基础到工程实践的完整技术架构
文章目录 一、声纹技术的理论基础与概念内核1.1 声纹的生物学本质与数学表征1.2 特征提取的理论基础与实现机制 二、声纹识别技术的演进逻辑与方法体系2.1 传统统计学方法的理论架构2.2 深度学习方法的技术革新2.3 损失函数的设计原理与优化策略 三、声纹识别系统的架构设计与模…...
机器学习与深度学习06-决策树02
目录 前文回顾5.决策树中的熵和信息增益6.什么是基尼不纯度7.决策树与回归问题8.随机森林是什么 前文回顾 上一篇文章地址:链接 5.决策树中的熵和信息增益 熵和信息增益是在决策树中用于特征选择的重要概念,它们帮助选择最佳特征进行划分。 熵&#…...

灌水论坛系统总体设计文档
一、实验题目 灌水论坛系统 二、实验目的 旨在通过一个相对完整且功能丰富的Web应用实例,全面地实践和巩固Web开发所需的各项核心技术和工程方法,从而提升其综合应用能力和解决实际开发问题的能力。它不仅仅是完成一个软件,更是一个学习、…...
【基础算法】模拟算法
文章目录 算法简介1. 多项式输出解题思路代码实现 2. 蛇形方阵解题思路代码实现 3. 字符串的展开解题思路代码实现 算法简介 模拟,顾名思义,就是题目让你做什么你就做什么,考察的是将思路转化成代码的代码能力。 这类题一般较为简单…...
SQL Server 事务详解:概念、特性、隔离级别与实践
一、事务的基本概念 事务(Transaction)是数据库操作的基本单位,它是由一组SQL语句组成的逻辑工作单元。事务具有以下关键特性,通常被称为ACID特性: 原子性(Atomicity):事务…...

【Linux】环境变量完全解析
9.环境变量 文章目录 9.环境变量一、命令行参数二、获取环境变量程序中获取环境变量1. 使用命令行参数2. 使用系统调用函数getenv("字符串");3. 使用系统提供的全局变量environ 命令行中查询环境变量 三、常见环境变量1. HOME2. OLDPWD3. PATH4. SHELL 四、环境变量与…...
Kubernetes资源申请沾满但是实际的资源占用并不多,是怎么回事?
Kubernetes资源申请沾满但是实际的资源占用并不多是Kubernetes资源管理中的一个常见误解。 K8s资源管理机制 资源请求(Requests) vs 实际使用量 从你的截图可以看到: K8s节点资源状态(第一张图): CPU请求量:13795…...