当前位置: 首页 > 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;做好日程安排对于日常工作至关重要…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...