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

p2p、分布式,区块链笔记: 通过libp2p的Kademlia网络协议实现kv-store

Kademlia 网络协议

  • Kademlia 是一种分布式哈希表协议和算法,用于构建去中心化的对等网络,核心思想是通过分布式的网络结构来实现高效的数据查找和存储。在这个学习项目里,Kademlia 作为 libp2p 中的 NetworkBehaviour的组成。

  • 以下这些函数或方法是根据 Kademlia 网络协议设计的,它们实现了基本的网络操作,包括获取数据记录、获取数据提供者、存储数据记录和开始提供数据等功能(这里只展示了项目中用到的函数,常用函数可以看libp2p Kademlia DHT 规范,更多函数可见如下图中的源码部分)。

在这里插入图片描述

1. get_record

kademlia.get_record(key, Quorum::One);
  • 作用: 从 Kademlia 网络中获取与指定 key 相关的记录。
  • 参数:
    • key: 要获取记录的键。
    • Quorum::One: 获取记录时所需的一致性要求,这里是指只需要从一个节点获取记录即可。
  • 实现逻辑:
    • 根据 Kademlia 协议,节点首先根据 key 计算出其对应的 K-bucket 或者具体的节点 ID,然后向网络中查找负责该 key 的节点。
    • 节点通过网络查询和消息传递机制,从负责节点处获取存储的记录。
    • 返回获取到的记录或者执行相应的处理逻辑。

2. get_providers

kademlia.get_providers(key);
  • 作用: 获取能够提供与指定 key 相关数据的节点信息(即数据的提供者)。
  • 参数:
    • key: 要获取提供者信息的数据的键。
  • 实现逻辑:
    • 类似于 get_record,节点根据 key 计算出其对应的 K-bucket 或者节点 ID。
    • 节点向网络发送查询请求,询问哪些节点能够提供与 key 相关的数据。
    • 返回能够提供数据的节点列表或者执行相应的处理逻辑。

3. put_record

let record = Record {key,value,publisher: None,expires: None,
};
kademlia.put_record(record, Quorum::One).expect("Failed to store record locally.");
  • 作用: 将指定的记录存储到 Kademlia 网络中。
  • 参数:
    • record: 包含要存储的数据信息的记录对象,包括 key(键)、value(值)、publisher(发布者,可能为空)、expires(过期时间,可能为空)等字段。
    • Quorum::One: 存储记录时的一致性要求,这里是指只需要将记录存储在一个节点即可。
  • 实现逻辑:
    • 节点根据 key 计算出对应的 K-bucket 或节点 ID。
    • 节点将 record 发送给负责存储该 key 的节点,并根据指定的一致性要求存储副本。
    • 返回存储成功或失败的结果,或者执行相应的处理逻辑。

4. start_providing

kademlia.start_providing(key).expect("Failed to start providing key");
  • 作用: 在 Kademlia 网络中开始提供指定 key 的数据。
  • 参数:
    • key: 要开始提供的数据的键。
  • 实现逻辑:
    • 节点将 key 注册为它可以提供的数据标识。
    • 当其他节点查询或需要该 key 的数据时,该节点将响应并提供相应的数据。
    • 返回启动提供成功或失败的结果,或者执行相应的处理逻辑。

kv数据库主体代码及注释

use async_std::io;
use futures::{prelude::*, select};
use libp2p::kad::record::store::MemoryStore;
use libp2p::kad::{record::Key, AddProviderOk, Kademlia, KademliaEvent, PeerRecord, PutRecordOk, QueryResult,Quorum, Record,
};
use libp2p::{development_transport, identity,mdns::{Mdns, MdnsConfig, MdnsEvent},swarm::SwarmEvent,NetworkBehaviour, PeerId, Swarm,
};
use std::error::Error;#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {env_logger::init();//  创建本地密钥,本地peer id和传输控制组件let local_key = identity::Keypair::generate_ed25519();let local_peer_id = PeerId::from(local_key.public());let transport = development_transport(local_key).await?;// 事件行为控制// We create a custom network behaviour that combines Kademlia and mDNS.#[derive(NetworkBehaviour)]// https://docs.rs/libp2p/latest/libp2p/swarm/trait.NetworkBehaviour.html#[behaviour(out_event = "MyBehaviourEvent")]//这个 "MyBehaviourEvent" 定义在下边的代码中// NetworkBehaviour这个trait将对所描述的结构体中的每个成员依次进行操作,例如 NetworkBehavior::poll它将首先轮询第一个结构成员,直到返回poll::Pending,然后再转到后面的成员。// 关于 #[behaviour(out_event = "MyBehaviourEvent")]中的out_event :The final out event. If we find a `#[behaviour(out_event = "Foo")]` attribute on the struct, we set `Foo` as the out event. Otherwise we use `()`.struct MyBehaviour {kademlia: Kademlia<MemoryStore>,mdns: Mdns,}#[allow(clippy::large_enum_variant)] //  #[allow()为Lint语法属性检查控制,https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes    //关于large_enum_variant 详见https://rust-lang.github.io/rust-clippy/master/index.html#/large_enum_variantenum MyBehaviourEvent {Kademlia(KademliaEvent),Mdns(MdnsEvent),}// 实现(impl)块,用于为类型KademliaEvent实现了From trait,使其能够被转换为类型MyBehaviourEvent。impl From<KademliaEvent> for MyBehaviourEvent {fn from(event: KademliaEvent) -> Self {MyBehaviourEvent::Kademlia(event)}}// 实现(impl)块,用于为类型  MdnsEvent   实现了From trait,使其能够被转换为类型MyBehaviourEvent。impl From<MdnsEvent> for MyBehaviourEvent {fn from(event: MdnsEvent) -> Self {MyBehaviourEvent::Mdns(event)}}// Create a swarm to manage peers and events.let mut swarm = {// Create a Kademlia behaviour.let store = MemoryStore::new(local_peer_id);let kademlia = Kademlia::new(local_peer_id, store);let mdns = Mdns::new(MdnsConfig::default()).await?;let behaviour = MyBehaviour { kademlia, mdns };Swarm::new(transport, behaviour, local_peer_id)};// 从命令行读取指令并赋值给可变变量"stdin"let mut stdin = io::BufReader::new(io::stdin()).lines().fuse();// Listen on all interfaces and whatever port the OS assigns.swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;// Kick it off.loop {select! {line = stdin.select_next_some() => handle_input_line(&mut swarm.behaviour_mut().kademlia, line.expect("Stdin not to close")),event = swarm.select_next_some() => match event { // swarm.select_next_some() 是一个方法,用于从一个事件流中获取下一个事件,后续送到match进行匹配SwarmEvent::NewListenAddr { address, .. } => {//当发生新的监听地址事件时println!("Listening in {:?}", address);},SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(MdnsEvent::Discovered(list))) => {// 发生mDNS服务发现事件时for (peer_id, multiaddr) in list {swarm.behaviour_mut().kademlia.add_address(&peer_id, multiaddr);}}SwarmEvent::Behaviour(MyBehaviourEvent::Kademlia(KademliaEvent::OutboundQueryCompleted { result, ..})) => {// 当发出的 Kademlia 查询完成时handle_query_result(&result);}_ => {} // 通配符模式,执行一个空的代码块}}}
}// 下面是两个辅助函数,一个根据不同的查询结果类型执行不同的逻辑,另一个处理从命令行输入的命令
fn handle_query_result(result: &QueryResult) {match result {...}
}fn handle_input_line(kademlia: &mut Kademlia<MemoryStore>, line: String) {let mut args = line.split(' ');match args.next() {...}
}

两个辅助函数

处理从命令行输入的命令

  • 这段 Rust 代码定义了一个函数 handle_input_line,用于处理从命令行读取的输入 line,并根据命令执行相应的操作。函数通过分割输入行来解析命令和参数,处理缺少参数的错误情况,并根据命令调用传入的 Kademlia 网络实例 (kademlia) 的相应方法。
fn handle_input_line(kademlia: &mut Kademlia<MemoryStore>, line: String) {// 将输入行按空格分割为多个参数let mut args = line.split(' ');// 匹配第一个参数(命令)match args.next() {Some("GET") => {// 如果命令是 "GET"let key = {// 尝试获取下一个参数作为键match args.next() {Some(key) => Key::new(&key), // 从字符串创建 Key 对象None => {// 如果未提供键,则打印错误并从函数返回eprintln!("缺少键");return;}}};// 调用 Kademlia 网络的 get_record 方法,传入指定的键和 Quorum::Onekademlia.get_record(key, Quorum::One);}Some("GET_PROVIDERS") => {// 如果命令是 "GET_PROVIDERS"let key = {// 尝试获取下一个参数作为键match args.next() {Some(key) => Key::new(&key), // 从字符串创建 Key 对象None => {// 如果未提供键,则打印错误并从函数返回eprintln!("缺少键");return;}}};// 调用 Kademlia 网络的 get_providers 方法,传入指定的键kademlia.get_providers(key);}Some("PUT") => {// 如果命令是 "PUT"let key = {// 尝试获取下一个参数作为键match args.next() {Some(key) => Key::new(&key), // 从字符串创建 Key 对象None => {// 如果未提供键,则打印错误并从函数返回eprintln!("缺少键");return;}}};let value = {// 尝试获取下一个参数作为值match args.next() {Some(value) => value.as_bytes().to_vec(), // 将值转换为字节向量None => {// 如果未提供值,则打印错误并从函数返回eprintln!("缺少值");return;}}};// 创建一个包含指定键、值及可选字段的 Record 对象let record = Record {key,value,publisher: None,expires: None,};// 在 Kademlia 网络中以 Quorum::One 一致性存储记录kademlia.put_record(record, Quorum::One).expect("本地存储记录失败。");}Some("PUT_PROVIDER") => {// 如果命令是 "PUT_PROVIDER"let key = {// 尝试获取下一个参数作为键match args.next() {Some(key) => Key::new(&key), // 从字符串创建 Key 对象None => {// 如果未提供键,则打印错误并从函数返回eprintln!("缺少键");return;}}};// 在 Kademlia 网络中开始提供指定的键kademlia.start_providing(key).expect("启动提供键失败");}_ => {// 如果命令不匹配预期的任何命令eprintln!("期望命令为 GET、GET_PROVIDERS、PUT 或 PUT_PROVIDER");}}

根据不同的查询结果类型执行不同的逻辑

fn handle_query_result(result: &QueryResult) {match result {QueryResult::GetProviders(Ok(ok)) => {for peer in &ok.providers {println!("Peer {:?} provides key {:?}",peer,std::str::from_utf8(ok.key.as_ref()).unwrap());}}QueryResult::GetProviders(Err(err)) => {eprintln!("Failed to get providers: {:?}", err);}QueryResult::GetRecord(Ok(ok)) => {for PeerRecord {record: Record { key, value, .. },..} in &ok.records{println!("Got record {:?} {:?}",std::str::from_utf8(key.as_ref()).unwrap(),std::str::from_utf8(&value).unwrap(),);}}QueryResult::GetRecord(Err(err)) => {eprintln!("Failed to get record: {:?}", err);}QueryResult::PutRecord(Ok(PutRecordOk { key })) => {println!("Successfully put record {:?}",std::str::from_utf8(key.as_ref()).unwrap());}QueryResult::PutRecord(Err(err)) => {eprintln!("Failed to put record: {:?}", err);}QueryResult::StartProviding(Ok(AddProviderOk { key })) => {println!("Successfully put provider record {:?}",std::str::from_utf8(key.as_ref()).unwrap());}QueryResult::StartProviding(Err(err)) => {eprintln!("Failed to put provider record: {:?}", err);}_ => {}}
}fn handle_input_line(kademlia: &mut Kademlia<MemoryStore>, line: String) {let mut args = line.split(' ');match args.next() {Some("GET") => {let key = {match args.next() {Some(key) => Key::new(&key),None => {eprintln!("Expected key");return;}}};kademlia.get_record(key, Quorum::One);}Some("GET_PROVIDERS") => {let key = {match args.next() {Some(key) => Key::new(&key),None => {eprintln!("Expected key");return;}}};kademlia.get_providers(key);}Some("PUT") => {let key = {match args.next() {Some(key) => Key::new(&key),None => {eprintln!("Expected key");return;}}};let value = {match args.next() {Some(value) => value.as_bytes().to_vec(),None => {eprintln!("Expected value");return;}}};let record = Record {key,value,publisher: None,expires: None,};kademlia.put_record(record, Quorum::One).expect("Failed to store record locally.");}Some("PUT_PROVIDER") => {let key = {match args.next() {Some(key) => Key::new(&key),None => {eprintln!("Expected key");return;}}};kademlia.start_providing(key).expect("Failed to start providing key");}_ => {eprintln!("expected GET, GET_PROVIDERS, PUT or PUT_PROVIDER");}}
}

运行示例

PS C:\Users\kingchuxing\Documents\learning-libp2p-main\rust> cargo run --example 04-kv-store
Listening in "/ip4/172.23.118.182/tcp/65055"
Listening in "/ip4/192.168.0.104/tcp/65055"
Listening in "/ip4/127.0.0.1/tcp/65055"
GET 123
Failed to get record: NotFound { key: Key(b"123"), closest_peers: [] }
PUT 123
缺少值
PUT 123 123456789
Failed to put record: QuorumFailed { key: Key(b"123"), success: [], quorum: 1 }
GET 123     
Got record "123" "123456789"
PUT_PROVIDER 234 //输入提供者
Successfully put provider record "234"
GET_PROVIDERS 234 //获取提供者
Peer PeerId("12D3KooWB7CFnrmeH5gzRxA4CYR2YTg2K3NMvNHP5dWDPFwAHY38") provides key "234"
GET 234
Failed to get record: NotFound { key: Key(b"234"), closest_peers: [] }

相关文章:

p2p、分布式,区块链笔记: 通过libp2p的Kademlia网络协议实现kv-store

Kademlia 网络协议 Kademlia 是一种分布式哈希表协议和算法&#xff0c;用于构建去中心化的对等网络&#xff0c;核心思想是通过分布式的网络结构来实现高效的数据查找和存储。在这个学习项目里&#xff0c;Kademlia 作为 libp2p 中的 NetworkBehaviour的组成。 以下这些函数或…...

ShareSDK iOS端如何实现小红书分享

下载SDK 请登陆官网 &#xff0c;找到SDK下载&#xff0c;勾选需要的平台下载 导入SDK &#xff08;1&#xff09;离线导入将上述下载到的SDK&#xff0c;直接将整个SDK资源文件拖进项目里&#xff0c;如下图&#xff1a; 并且勾选以下3个选项 在点击Finish&#xff0c;…...

算法day1 两数之和 两数相加 冒泡排序 快速排序

两数之和 最简单的思维方式肯定是去凑两个数&#xff0c;两个数的和是目标值就ok。这里两遍for循环解决。 两数相加 敲了一晚上哈哈&#xff0c;结果超过int范围捏&#xff0c;难受捏。 public class Test2 {public static void main(String[] args) { // ListNode l1 …...

Rust监控可观测性

可观测性 在监控章节的引言中&#xff0c;我们提到了老板、前端、后端眼中的监控是各不相同的&#xff0c;那么有没有办法将监控模型进行抽象、统一呢&#xff1f; 来简单分析一下&#xff1a; 业务指标实时展示&#xff0c;这是一个指标型的数据( metric )手机 APP 上传的数…...

SVN 的忽略(Ignore)和递归(Recursively)以及忽略部分

SVN中忽略大家经常用到&#xff0c;但总是似懂非懂&#xff0c;下面就详细展开说明一下忽略如何设置。 两个忽略 通常设置忽略都是文件夹和里面的文件都忽略。 设置忽略我们通常只需要鼠标右键点击忽略就可以了&#xff0c;如图&#xff1a; 第一个忽略用的最多&#xff0c;…...

vue3开发过程中遇到的一些问题记录

问题&#xff1a; vue3在使用 defineProps、defineEmits、defineExpose 时不需要import&#xff0c;但是 eslint会报错error defineProps is not defined no-undef 解决方法&#xff1a; 安装 vue-eslint-parser 插件&#xff0c;在 .eslintrc.js 文件中添加配置 parser: vue-e…...

Jedis、Lettuce、RedisTemplate连接中间件

jedis就像jdbc一样&#xff0c;用于两个端直接的连接。 1.创建Spring项目 这里不过多赘述... 2.导入连接工具jedis 在pom文件中导入jedis的依赖。 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version&…...

【C++】继承(详解)

前言&#xff1a;今天我们正式的步入C进阶内容的学习了&#xff0c;当然了既然是进阶意味着学习难度的不断提升&#xff0c;各位一起努力呐。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &#x1f448; &#…...

网络io与select,poll,epoll

前言 网络 IO&#xff0c;会涉及到两个系统对象&#xff0c;一个是用户空间调用 IO 的进程或者线程&#xff0c;另一个是内核空间的内核系统&#xff0c;比如发生 IO 操作 read 时&#xff0c;它会经历两个阶段&#xff1a; 1. 等待数据准备就绪 2. 将数据从内核拷贝到进程或…...

【Linux】多线程(一万六千字)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 线程的概念 线程的理解(Linux系统为例) 在Linux系统里如何保证让正文部分的代码可以并发的去跑呢&#xff1f; 为什么要有多进程呢&#xff1f; 为…...

sh脚本笔记2

test条件测试 语法 条件测试语法说明语法1&#xff1a;test <测试表达式>这是利用test命令进行条件测试表达式的方法。test命令和“<测试表达式>”之间至少有一个空格语法2&#xff1a;[ <测试表达式> ]这是通过[]&#xff08;单中括号&#xff09;进行条件…...

js替换对象里面的对象名称

data为数组&#xff0c;val为修改前的名称&#xff0c;name为修改后的名称 JSON.parse(JSON.stringify(data).replace(/val/g, name)) &#xff1b; 1.替换data里面的对象tenantInfoRespVO名称替换成tenantInfoUpdateReqVO 2.替换语句&#xff1a; 代码可复制 let tenantInf…...

鸿蒙开发设备管理:【@ohos.settings (设置数据项名称)】

设置数据项名称 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块提供设置数据项的访问功能相关接口的说明及示例。 导入模块 import settings from ohos.settings;settings.getUri…...

STM32之五:TIM定时器(2-通用定时器)

目录 通用定时器&#xff08;TIM2~5&#xff09;框图 1、 输入时钟源选择 2、 时基单元 3 、输入捕获&#xff1a;&#xff08;IC—Input Capture&#xff09; 3.1 输入捕获通道框图&#xff08;TI1为例&#xff09; 3.1.1 滤波器&#xff1a; 3.1.2 边沿检测器&#xf…...

【分布式系统】监控平台Zabbix对接grafana

以前两篇博客为基础 【分布式系统】监控平台Zabbix介绍与部署&#xff08;命令截图版&#xff09;-CSDN博客 【分布式系统】监控平台Zabbix自定义模版配置-CSDN博客 一.安装grafana并启动 添加一台服务器192.168.80.104 初始化操作 systemctl disable --now firewalld set…...

操作系统真象还原:编写硬盘驱动程序

第13章-编写硬盘驱动程序 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 13.1 硬盘及分区表 13.1.1 创建从盘及获取安装的磁盘数 要实现文件系统&#xff0c;必须先有个磁盘介质&#xff0c;虽然咱们己经有个虚拟磁盘 hd60M.img&#xff0c;但它只…...

firewalld防火墙(二)

一&#xff1a;firewalld高级配置 1&#xff1a;关于iptables的知识 iptables 是Linux系统中传统的命令行防火墙管理工具&#xff0c;它基于内核的netfilter框架工作&#xff0c;用于配置和管理网络规则集&#xff0c;比如过滤&#xff08;允许/拒绝&#xff09;进出的数据包…...

Android-悬浮窗口

在Android系统中&#xff0c;如果应用需要弹出一个悬浮窗口&#xff0c;就需要申请一项特殊权限 <uses-permission android:name"android.permission.SYSTEM_ALERT_WINDOW"/>在Android O之前的系统中申请了该权限后&#xff0c;再给对应的window设置 WindowM…...

打破僵局:Foxit Reader无法打开的终极解决方案

打破僵局&#xff1a;Foxit Reader无法打开的终极解决方案 在数字化阅读时代&#xff0c;Foxit Reader作为一款广受欢迎的PDF阅读器&#xff0c;其打不开的问题无疑会给用户带来诸多不便。本文将为您提供全面的解决方案&#xff0c;从基础检查到高级技巧&#xff0c;确保您能够…...

[调试] JTAG下运行正常,从QSPI或者SD卡启动则无响应,如何查找问题

[调试] JTAG下运行正常&#xff0c;从QSPI或者SD卡启动则无响应&#xff0c;如何查找问题 一、问题现象二、用自定义fsbl替代系统默认的fsbl1. 新建fsbl_new2. 如果提示缺少xilffs库3. 使能调试信息输出 三. 启动成功和失败情况下的典型输出1. JTAG启动模式: 正常加载2. QSPI启…...

Linux内核 -- 多线程之wait_event用法

Linux Kernel 中 wait_event 的高级用法及注意事项 在Linux内核编程中&#xff0c;wait_event 系列函数是用于实现进程等待和事件通知机制的重要工具。本文将详细介绍 wait_event 的高级用法以及注意事项。 1. 基本用法 wait_event 系列宏主要包括以下几种形式&#xff1a; …...

双指针系列第 8 篇:盛水最多的容器。几句话讲明白!

Leetcode 题目链接 思路 取首尾双指针和水量如下所示&#xff0c;设高度函数为 h ( i ) h(i) h(i)&#xff0c;在下图中 h ( l ) < h ( r ) h(l) < h(r) h(l)<h(r)。 观察以 l l l 为左边界所能构成的其他水量&#xff0c;与矮的右边界搭配结果如下。 与高的…...

c++高阶-1-模板

文章目录 模板一、模板基本语法二、函数模板1.基本语法2.函数模板注意事项3.普通函数和函数模板区别4.普通函数和函数模板调用规则 三、类模板1.基本语法2.类模板和函数模板的区别3.类模板中成员函数调用时机4.类模板对象做函数参数5.类模板与继承6.成员函数的类外实现 模板 一…...

.net core 的 winform 的 浏览器控件 WebView2

在.NET Core WinForms应用程序中&#xff0c;没有直接的“浏览器控件”&#xff0c;因为WinForms不支持像WebBrowser控件那样的功能。但是&#xff0c;你可以使用WebView2控件&#xff0c;它是一个基于Chromium的浏览器内核&#xff0c;可以在WinForms应用程序中嵌入Web内容。 …...

Django QuerySet对象,all()方法

all()方法 在Django中&#xff0c;all()方法是QuerySet对象的一个方法&#xff0c;用于获取模型的所有实例。 当你调用ModelName.objects.all()时&#xff0c;Django会生成一个SQL查询&#xff0c;从数据库中获取该模型的所有记录&#xff0c;并返回一个QuerySet对象&#xf…...

自动生成网站sitemap

要在 Next.js 和 Contentlayer 项目中实现自动生成 Sitemap 的功能&#xff0c;你可以编写一个脚本&#xff0c;在每次生成文档后自动生成 Sitemap。以下是一个示例脚本&#xff0c;你可以根据自己的需求进行调整。 步骤 1&#xff1a;安装必要的依赖 首先&#xff0c;你需要…...

中国经济昆虫志(55卷)

中国经济昆虫志&#xff0c;共55卷&#xff0c;内容包括概述、形态特征、分类等。各级分类单元均编有检索表&#xff0c;每个种有特征描述、地理分布&#xff0c;有的还记载有生活习性和防治方法。为便于鉴定&#xff0c;绘制有特征图和彩色图。 包括鞘翅目天牛科、半翅目蝽科、…...

linux环境安装elasticsearch缓存数据库和Kibana客户端

linux环境安装elasticsearch缓存数据库&#xff0c;今天我们安装7.17.18版本&#xff0c;并分析遇到的问题。 一、elasticsearch安装运行 1、直接下载 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.18-linux-x86_64.tar.gz2、解压 tar -…...

OpenSSL的一些使用案例

目录 一、介绍 二、基本使用 1、Shell &#xff08;1&#xff09;文件加解密 &#xff08;2&#xff09;生成密钥文件 2、API &#xff08;1&#xff09;md5sum &#xff08;2&#xff09;AES256加解密 一、介绍 本篇博客重点不是详细描述 OpenSSL 的用法&#xff0c;只…...

常用字符串方法<python>

导言 在python中内置了许多的字符串方法&#xff0c;使用字符串方法可以方便快捷解决很多问题&#xff0c;所以本文将要介绍一些常用的字符串方法。 目录 导言 string.center(width[,fillchar]) string.capitalize() string.count(sub[,start[,end]]) string.join(iterabl…...