当前位置: 首页 > news >正文

分布式系统通信解决方案:Netty 与 Protobuf 高效应用

分布式系统通信解决方案:Netty 与 Protobuf 高效应用

一、引言

在现代网络编程中,数据的编解码是系统设计的一个核心问题,特别是在高并发和低延迟的应用场景中,如何高效地序列化和传输数据对于系统的性能至关重要。随着分布式系统和微服务架构的广泛应用,跨平台、高效的序列化方案变得愈加重要。

Protocol Buffers(简称 Protobuf)是 Google 开发的一个高效的序列化工具。它能够将结构化数据序列化为紧凑的二进制格式,具有以下显著优势:

  1. 高效紧凑:Protobuf 使用二进制格式,比传统的 JSON 和 XML 更紧凑,占用更少的存储空间,传输速度更快,适用于高负载、高频繁的网络通信。
  2. 跨语言支持:Protobuf 支持多种编程语言,包括 Java、Python、C++、Go 等,使得它在异构系统之间传输数据时具有极好的兼容性。
  3. 灵活性强: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 的使用流程非常简单:

  1. 定义 .proto 文件:描述数据结构(如消息类型、字段名称和数据类型)。
  2. 生成代码:使用 Protobuf 编译器(protoc)将 .proto 文件编译成对应语言的代码。
  3. 序列化与反序列化:在应用程序中,使用生成的代码进行数据的序列化和反序列化。

这种工作流使得 Protobuf 成为在分布式系统、微服务架构和跨平台通信中,处理数据交换的理想选择。


三、在 Netty 中使用 Protobuf

Netty 提供了对 Protobuf 的原生支持,主要通过以下编解码器实现:

  1. ProtobufEncoder: 将消息序列化为二进制数据。
  2. ProtobufDecoder: 将二进制数据反序列化为 Protobuf 对象。

此外,为了解决 TCP 拆包与黏包问题,Netty 中通常配合使用 LengthFieldBasedFrameDecoderLengthFieldPrepender


四、Protobuf 在 Netty 中的基础应用

下面通过一个完整的示例展示如何在 Netty 中结合 Protobuf 实现客户端与服务端的数据传输。

1. Protobuf 工具与 Java 8 的兼容性

Protobuf 编译器(protoc 生成的代码与 Java 运行时兼容。由于 Java 8 是较旧的版本,需要确保以下两点:

  1. 选择的 Protobuf 编译器生成的代码与 Java 8 的字节码兼容。
  2. 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 编译器

  1. 下载链接:

    • 从 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 支持多种数据类型,包括基本类型(如 int32stringbool 等)以及复合类型(如 messageenumrepeated)。

Protobuf 文件示例

以下是拆分后的两个 .proto 文件:

  1. 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:表示操作类型,可能的值有 addremove,类型为 string
  1. 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 是一个高性能的网络框架,适用于处理大量并发连接和高效数据传输。通过将 NettyProtobuf 结合,开发者可以在保证高性能的同时,还能有效地进行数据序列化和反序列化。结合 ProtobufEncoderProtobufDecoder,数据的传输效率大大提升,特别是当需要传输大量结构化数据时。

Netty 提供了原生支持,简化了 Protobuf 的使用,只需通过简单的编码解码器配置,就可以实现 Protobuf 消息的高效传输。此外,Netty 的 LengthFieldBasedFrameDecoderLengthFieldPrepender 解码器,帮助我们解决了 TCP 拆包黏包的问题,确保消息完整性和传输的可靠性。

3. 实践中的建议

在实际开发中,结合 NettyProtobuf 的使用,可以进一步优化网络服务的性能:

  • 性能调优:根据业务需求,可以调整 Protobuf 的编解码策略,例如通过压缩数据来减少带宽占用;在高并发场景下,可以使用 Protobuf 的压缩选项来进一步提高传输效率。
  • 多种协议的结合:除了 Protobuf,Netty 还支持其他协议(如 JSON、XML、Thrift 等),你可以根据不同的应用场景,选择适合的数据格式进行组合。
  • 错误处理与安全:在处理实际应用时,务必考虑错误处理和安全性。例如,使用适当的验证机制来防止恶意数据注入,并确保网络连接的安全性(如使用 SSL/TLS 加密)。

4. 高效的跨平台通信

通过本篇博客,你已经学会了如何使用 ProtobufNetty 实现高效、可扩展的跨平台网络通信。无论是在微服务架构中,还是在大规模的分布式系统中,利用这两者的结合,都能够实现高效的消息传递,保证系统的高并发和低延迟特性。

5. 后续学习与扩展

如果你希望进一步优化系统的性能,或在更复杂的场景中使用 NettyProtobuf,可以从以下几个方向进行学习和扩展:

  • Protobuf 高级特性:深入学习 Protobuf 的更多高级特性,如自定义序列化和反序列化逻辑、扩展机制等。
  • Netty 高级用法:学习 Netty 更高级的特性,例如自定义协议处理、事件驱动模型的优化、流量控制等。
  • 性能优化:根据实际需求,结合负载均衡、数据压缩和缓存机制等技术,进一步提高系统的吞吐量和响应速度。

通过不断实践和优化,你将能够构建更加高效、灵活和可扩展的网络服务。

相关文章:

分布式系统通信解决方案:Netty 与 Protobuf 高效应用

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

计算机网络 (54)系统安全:防火墙与入侵检测

前言 计算机网络系统安全是确保网络通信和数据不受未经授权访问、泄露、破坏或篡改的关键。防火墙和入侵检测系统&#xff08;IDS&#xff09;是维护网络系统安全的两大核心组件。 一、防火墙 定义与功能 防火墙是一种用来加强网络之间访问控制的特殊网络互联设备&#xff0c;它…...

stack底层实现细节

一、stack 和 queue 在 STL 中 stack 和 queue 已经不算是容器了&#xff0c;而是容器适配器&#xff0c;适配器模式也是常用的模式之一&#xff0c;体现在 stack 和 queue 中就是他们两个的实现不是单独写的&#xff0c;而是复用了前面合适的优秀的STL 容器的代码而实现的具有…...

工业相机 SDK 二次开发-Halcon 插件

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

map和set的使用(一)详解

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

ARP 表、MAC 表、路由表、跨网段 ARP

文章目录 一、ARP 表1、PC2、路由器 - AR22203、交换机 - S57004、什么样的设备会有 ARP 表&#xff1f; 二、MAC 表什么样的设备会有 MAC 表&#xff1f; 三、路由表什么样的设备会有路由表&#xff1f; 四、抓取跨网段 ARP 包 所谓 “透明” 就是指不用做任何配置 一、ARP 表…...

37.构造回文字符串问题|Marscode AI刷题

1.题目 问题描述 小C手中有一个由小写字母组成的字符串 s。她希望构造另一个字符串 t&#xff0c;并且这个字符串需要满足以下几个条件&#xff1a; t 由小写字母组成&#xff0c;且长度与 s 相同。t 是回文字符串&#xff0c;即从左到右与从右到左读取相同。t 的字典序要小…...

ssm-mybatisPlus学习笔记

注意&#xff01;mybatisPlus只能够进行单表操作&#xff0c;其他的仍需要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)&#xff0c;给定 a a a、 b b b和 m m m&#xff0c;找到一个整数 x x x使得该方程成立&#xff0c;即使得 a x m o d m b ax~mod~mb ax mod mb&#xff0c;随便返回任何一个…...

线性规划:机器学习中的优化利器

一、线性规划的基本概念 线性规划&#xff08;Linear Programming, LP&#xff09;是运筹学中数学规划的一个重要分支&#xff0c;用于在一组线性不等式的约束条件下&#xff0c;找到线性目标函数的最大值或最小值。其问题可以表述为&#xff1a; 在一组线性约束条件 s.t.&am…...

Ubuntu开发中的问题

1.退出anaconda指令&#xff1a;conda deactivate 2.Linux系列&#xff1a;一打开终端就默认进入conda的base环境&#xff0c;取消方法 在终端输入conda config --show&#xff0c;会显示所有的配置信息,然后利用conda config --set来修改此配置&#xff1a; conda config --se…...

MAC 地址转换为标准大写格式

// ConvertToStandardMac 将 MAC 地址转换为标准格式&#xff0c;确保每个字节都是两位&#xff0c;并且字母是大写的 func ConvertToStandardMac(mac string) (string, error) { // 分割 MAC 地址的每一部分 parts : strings.Split(mac, ":") // 确保每部分是两…...

使用插件SlideVerify实现滑块验证

作者gitee地址&#xff1a;https://gitee.com/monoplasty/vue-monoplasty-slide-verify 使用步骤&#xff1a; 1、安装插件 npm install --save vue-monoplasty-slide-verify 2、在main.js中进行配置 import SlideVerify from vue-monoplasty-slide-verify; Vue.use(SlideV…...

深入探索 Nginx 的高级用法:解锁 Web 服务器的强大潜能

在当下互联网技术飞速发展的浪潮中&#xff0c;Nginx 凭借其轻量级、高性能的特性&#xff0c;在 Web 服务器和反向代理服务器领域脱颖而出&#xff0c;成为众多开发者和运维工程师的得力工具。它不仅能高效处理静态资源&#xff0c;在负载均衡、反向代理等方面也表现出色。然而…...

(01)搭建开发环境

1.安装虚拟机软件 VMware Workstation Pro 17 2.虚拟机安装ubuntu20.4系统 3.安装VMtools工具 4.安装vim编辑器 sudo apt install vim 4.安装SSH服务 选择下载源为&#xff1a;http://mirrors.aliyun.com/ubuntu在线安装&#xff1a;sudo apt-get install openssh-serv…...

Win11桌面右键刷新选项在二级界面的修正方法

win10已经被弃用了&#xff0c;现在的win11在桌面右键时&#xff0c;“刷新”按钮在二级界面。除此以外&#xff0c;在资源管理器中浏览文件的时候&#xff0c;很多其他选项也都被放在了二级界面&#xff0c;非常不方便。接下来介绍一个把右键菜单栏中的所有选项都显示在一级界…...

配电室防静电地板通常用哪种

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

【重庆市乡镇界】面图层shp格式arcgis数据乡镇名称和编码wgs84坐标无偏移内容测评

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

68,[8] BUUCTF WEB [RoarCTF 2019]Simple Upload(未写完)

<?php // 声明命名空间&#xff0c;遵循 PSR-4 自动加载规范&#xff0c;命名空间为 Home\Controller namespace Home\Controller;// 导入 Think\Controller 类&#xff0c;以便扩展该类 use Think\Controller;// 定义 IndexController 类&#xff0c;继承自 Think\Control…...

Windows电脑桌面记录日程安排的提醒软件

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

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...