【常见开源库的二次开发】基于openssl的加密与解密——SHA算法源码解析(六)

news2024/11/14 20:34:15

目录

一、SHA-1算法分析:

1.1 Merkle Tree可信树

1.2 源码实现:

1.3 哈希计算功能

1.4 两种算法的区别: 

1.4.1 目的

 1.4.2 实现机制

1.4.3 输出

1.4.4 应用场景:

1.4 运行演示:

二、SHA-2算法分析: 

2.1哈希算法简介 

 2.2 比特币挖矿与工作量证明

2.3 安全性 

 2.4 SHA-2算法概述

2.4.1. 消息填充

2.4.2 初始化链接变量缓冲区(SHA-256)

2.4.3 SHA-512 处理

2.4.4 SHA-384 和 SHA-512 的迭代函数

 三、模拟比特币挖矿

3.1 区块的结构

3.2. 挖矿过程

3.3 TARGET 的计算

3.4 源码演示

 3.5 算法思路

1. 目标定义:

2. 数据准备:

3. 哈希计算:

4. 有效性检查:

5. 输出结果:

总结:


一、SHA-1算法分析:

安全散列算法是一种加密散列函数,生成固定长度的散列值(或摘要),用于确保数据完整性和验证数据的真实性。

 SHA-1是SHA系列中的一种,产生160比特(20字节)的散列值。其散列值通常表示为40个十六进制字符。SHA-1的散列值格式为`H0 H1 H2 H3 H4`,每个部分代表散列的不同部分。

 尽管SHA-1曾被广泛使用,但它已经被证明存在弱点,尤其是对碰撞攻击的脆弱性。在2005年,研究人员王小云成功攻破了SHA-1,展示了其产生相同散列值的可能性,这就是所谓的“碰撞”。消息摘要是通过散列函数生成的数据摘要,它是输入数据的唯一表示。消息摘要用于确保数据在传输或存储过程中未被篡改。

MD4是早期的散列算法,而MD5是由MD4算法衍生而来。MD5生成128比特的散列值,广泛用于数据完整性校验,但同样也被发现存在安全漏洞。SHA系列,特别是SHA-1,确实是受到MD4的影响。尽管SHA-1和MD5具有不同的结构和输出长度,但它们在设计理念上有相似之处。

1.1 Merkle Tree可信树

Merkle树是一种哈希树,叶子节点代表数据块(如交易)的哈希值,而非叶子节点的哈希值则是其子节点哈希值的组合。

Merkle树的底部是交易的哈希(即叶子节点),而每对叶子节点的哈希值会组合成其父节点的哈希,直到最终形成根哈希(Merkle根)。这个根哈希代表了整个树的所有交易摘要。

在比特币中,交易哈希使用双SHA-256哈希算法进行计算。即每一笔交易在生成哈希时,首先计算SHA-256哈希,然后再对该哈希值计算一次SHA-256,从而增强安全性。

1.2 源码实现:

#include <iostream> // 引入输入输出流库,以便使用 cout 和 cerr
#include <openssl/md5.h> // 引入 OpenSSL 的 MD5 相关函数
#include <openssl/sha.h> // 引入 OpenSSL 的 SHA 相关函数
#include <fstream> // 引入文件流库,以便读取文件
#include <iomanip> // 引入用于格式化输入输出的库,例如设置输出宽度
#include <thread> // 引入线程库,以便使用睡眠功能
#include <vector> // 引入 STL 的 vector 容器

using namespace std; // 使用标准命名空间,方便后续使用标准库中的元素

// 计算文件的MD5哈希值
string GetFileListHash(const string& filepath)
{
    // 以二进制方式打开文件
    ifstream ifs(filepath, ios::binary); // 创建文件流对象以二进制模式打开指定路径的文件
    if (!ifs) { // 检查文件是否成功打开
        cerr << "Failed to open file: " << filepath << endl; // 输出错误信息到标准错误流
        return ""; // 返回空字符串表示失败
    }

    int block_size = 128; // 定义一次读取的块大小为 128 字节
    unsigned char buf[block_size] = { 0 }; // 创建一个缓冲区用于存放读取的数据,初始化为 0
    unsigned char out[MD5_DIGEST_LENGTH] = { 0 }; // 创建输出数组,用于存放 MD5 哈希结果

    MD5_CTX c; // 定义 MD5 上下文结构体
    MD5_Init(&c); // 初始化 MD5 上下文

    while (!ifs.eof()) { // 当文件没有到达末尾时循环
        ifs.read(reinterpret_cast<char*>(buf), block_size); // 从文件中读取数据到 buf 缓冲区
        int read_size = ifs.gcount(); // 获取实际读取的字节数
        if (read_size > 0) { // 如果读取的字节数大于 0
            MD5_Update(&c, buf, read_size); // 将读取的数据更新到 MD5 上下文中
        }
    }

    MD5_Final(out, &c); // 完成 MD5 计算并将结果存入 out 数组
    ifs.close(); // 关闭文件流

    return string(reinterpret_cast<char*>(out), MD5_DIGEST_LENGTH); // 将 MD5 结果转换为字符串并返回
}

// 文件可信树 Hash
std::string GetFileMerkleHash(std::string filepath) {
    std::string hash; // 定义哈希字符串
    std::vector<std::string> hashes; // 定义一个字符串向量用于存放中间哈希值
    std::ifstream ifs(filepath, std::ios::binary); // 以二进制方式打开文件

    if (!ifs) return hash; // 如果文件未打开,返回空哈希值

    unsigned char buf[1024] = { 0 }; // 创建缓冲区用于读取文件数据
    unsigned char out[SHA_DIGEST_LENGTH]; // 创建输出数组,用于存放 SHA1 哈希结果

    int block_size = 128; // 定义一次读取的块大小为 128 字节
    while (!ifs.eof()) { // 当文件没有到达末尾时循环
        ifs.read(reinterpret_cast<char*>(buf), block_size); // 从文件中读取数据到 buf
        int read_size = ifs.gcount(); // 获取实际读取的字节数
        if (read_size <= 0) break; // 如果没有读取到数据,跳出循环

        SHA1(buf, read_size, out); // 计算 SHA1 哈希并存储到 out 数组中
        hashes.push_back(std::string(reinterpret_cast<char*>(out), SHA_DIGEST_LENGTH)); // 将哈希值转换为字符串并添加到 hashes 向量中
    }

    // 计算 Merkle 哈希
    while (hashes.size() > 1) { // 当 hashes 向量中有多个哈希值时
        if (hashes.size() & 1) { // 如果 hashes 的大小是奇数
            hashes.push_back(hashes.back()); // 将最后一个哈希值复制一份到末尾,保证成对
        }

        for (size_t i = 0; i < hashes.size() / 2; i++) { // 遍历哈希值的成对组合
            std::string tmp_hash = hashes[i * 2]; // 获取第一个哈希值
            tmp_hash += hashes[i * 2 + 1]; // 拼接第二个哈希值
            SHA1(reinterpret_cast<const unsigned char*>(tmp_hash.data()), tmp_hash.size(), out); // 计算拼接后字符串的 SHA1 哈希
            hashes[i] = std::string(reinterpret_cast<char*>(out), SHA_DIGEST_LENGTH); // 更新哈希值到 hashes 向量
        }
        hashes.resize(hashes.size() / 2); // 将 hashes 向量的大小减半
    }

    if (hashes.size() == 0) return hash; // 如果 hashes 向量为空,返回空哈希值
    return hashes[0]; // 返回 Merkle 树的根哈希值
}

// 以十六进制格式输出数据
void PrintHex(const string& data) {
    for (unsigned char c : data) { // 遍历数据中的每一个字节
        cout << hex << setw(2) << setfill('0') << (int)c; // 将每个字节以十六进制格式输出
    }
    cout << endl; // 输出换行符
}

int main() {
    cout << "Test Hash" << endl; // 输出测试信息

    unsigned char data[] = "测试MD5数据"; // 定义要进行MD5哈希的数据
    unsigned char out[MD5_DIGEST_LENGTH] = { 0 }; // 创建输出数组,用于存放 MD5 哈希结果
    int len = sizeof(data) - 1; // 计算数据长度,减去 1 是因为 sizeof 包含字符串末尾的 '\0'

    // 对数据进行 MD5 哈希计算
    MD5(data, len, out); // 计算 MD5 哈希
    // 输出哈希结果
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { // 遍历哈希结果
        cout << hex << setw(2) << setfill('0') << (int)out[i]; // 输出哈希值的每一个字节
    }
    cout << endl; // 输出换行符

    data[1] = 9; // 修改数据
    MD5(data, len, out); // 再次计算 MD5 哈希
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { // 遍历哈希结果
        cout << hex << setw(2) << setfill('0') << (int)out[i]; // 输出新的哈希值
    }
    cout << endl; // 输出换行符

    string filepath = "/home/book/Desktop/test.txt"; // 指定要读取的文件路径
    auto hash1 = GetFileListHash(filepath); // 计算文件的 MD5 哈希值
    PrintHex(hash1); // 输出文件的哈希值

    for (;;) { // 无限循环
        auto hash = GetFileListHash(filepath); // 重新计算文件哈希值
        auto thash = GetFileMerkleHash(filepath); // 计算文件的 Merkle 哈希值

        if (hash != hash1) { // 如果文件哈希值与之前的哈希不相等
            cout << "文件被修改" << endl; // 输出文件被修改的警告
            cout << "HashList:\t"; // 输出哈希列表的提示
            PrintHex(hash); // 输出新的哈希值
            hash1 = hash; // 更新之前的哈希值

            cout << "MerkleTree:\t"; // 输出 Merkle 树的提示
            PrintHex(thash); // 输出 Merkle 哈希值
        }
        this_thread::sleep_for(chrono::seconds(1)); // 每秒检查一次
    }

    return 0; // 程序正常结束
}

makefile

first_openss:test_openssl.cpp
	g++ $^ -o $@ -I/usr/local/include -L/usr/local/lib -lcrypto -std=c++11

1.3 哈希计算功能

1. MD5 哈希计算 (GetFileListHash 函数)

  • 该函数用于计算指定文件的 MD5 哈希值
  • 以二进制模式打开文件,逐块读取文件数据(128 字节为一块),并将读取的数据传递给 MD5 上下文进行更新。
  • 文件读取完成后,调用 MD5_Final 完成哈希计算,并返回哈希值。

2. Merkle 哈希计算 (GetFileMerkleHash 函数)

  • 该函数实现了基于文件内容的 Merkle 树哈希计算
  • 逐块读取文件数据,并对每一块数据计算 SHA-1 哈希,存储在一个 std::vector 中。
  • 通过将相邻的哈希值成对拼接并计算新的 SHA-1 哈希,逐步构建 Merkle 树,直到只剩下一个根哈希。

1.4 两种算法的区别: 

1.4.1 目的

(1). MD5 哈希计算 (GetFileListHash 函数)

  • 主要用于生成文件的唯一标识符(哈希值),以便快速验证文件内容的完整性。
  • 当文件内容发生变化时,MD5 哈希值会变化,从而可以用于检测文件是否被修改。

(2). Merkle 哈希计算 (GetFileMerkleHash 函数) 

  • 主要用于构建 Merkle 树,提供一种结构化的方法来验证数据完整性和一致性。
  • Merkle 树允许验证文件中的特定部分(例如,分块数据),并且在分布式系统(如区块链)中非常常见,能够支持高效的证明和检查。

 1.4.2 实现机制

(1). MD5 哈希计算

  • 整个文件数据一次性地计算一个 MD5 哈希值。
  • 逐块读取文件(每块 128 字节),将读取到的数据传递到 MD5 上下文进行更新。
  • 最终通过调用 MD5_Final 完成哈希计算,返回一个固定大小的 128 位(16 字节)MD5 哈希值。

(2). Merkle 哈希计算

  • 将文件分成多个块,对每个块计算 SHA-1 哈希,得到每个块的哈希值,并将它们存储在一个向量中。
  • 接着,将相邻的哈希值成对拼接,计算新的 SHA-1 哈希,重复这个过程,最终构建出一个根哈希(Merkle 根)。
  • 这一过程允许逐渐合并和验证多个哈希,能够高效地构建树状结构,有助于快速验证数据的完整性。

1.4.3 输出

(1). MD5 哈希计算:

  • 输出的结果是一个单一的 MD5 哈希值,通常以十六进制字符串形式表示。

(2). Merkle 哈希计算

  • 输出的是一个根哈希值(Merkle 根),它代表了整个文件内容的哈希结构,可以视为整个文件的完整唯一标识。

1.4.4 应用场景:

(1). MD5 哈希计算 更加简单,主要用于快速验证文件是否已被修改,适合单一文件检查。

(2). Merkle 哈希计算 则更复杂,适用于需要验证数据完整性和一致性的场景,尤其是当涉及多个数据块或分布式系统时。它通过构建树形结构提高了数据验证的效率和灵活性。

1.4 运行演示:

二、SHA-2算法分析: 

SHA-256、SHA-384 和 SHA-512 是三种不同的安全哈希算法,它们都是 SHA-2(安全哈希算法2)系列的一部分。它们在比特币挖矿的工作量证明(Proof of Work)机制中扮演着重要角色。

2.1哈希算法简介 

1. SHA-256

  • 输出长度:256位(32字节)。
  • 是比特币网络中使用的主要哈希函数。
  • 具有较强的安全性,目前尚未发现有效的攻击方式。

 2. SHA-384:

  • 输出长度:384位(48字节)。
  • 通常用于需要更高安全性要求的场合,但在比特币挖矿中并不常用。

3.  SHA-512:

  • 输出长度:512位(64字节)。
  • 也提供了较高的安全性和抗碰撞能力,适用于对数据完整性有极高要求的应用场景。

 2.2 比特币挖矿与工作量证明

1. 工作量证明机制:

  • 在比特币网络中,工作量证明是用于验证和确认交易的一种机制。矿工通过计算哈希值来竞争生成新区块。
  • 矿工需要找到一个满足特定条件的哈希值(即小于某个目标值)。为此,他们会不断调整输入数据(包括随机数 nonce)并计算哈希,直到找到符合条件的哈希值。

2. SHA-256 在比特币挖矿中的应用:

  • 比特币挖矿主要使用 SHA-256 算法。矿工通过对区块头信息应用 SHA-256 哈希,试图找到一个符合难度目标的哈希值。
  • 挖矿的过程是一个计算密集型的过程,不断地尝试生成一个新的哈希值,每一次尝试都需要消耗计算资源。这也是导致比特币挖矿需要大量电力和硬件资源的原因。

 

2.3 安全性 

  • 目前,SHA-2 系列(包括 SHA-256、SHA-384 和 SHA-512)尚未被成功攻克。尽管 SHA-1 在安全性上已被广泛认为不再安全,但 SHA-2 仍然被认为是安全的。
  • 这些哈希算法能够有效防止碰撞攻击(即不可能找到两个不同的输入生成相同的哈希值),并且提供良好的抗预映射攻击(即从哈希值推断出原始输入的难度)。

 2.4 SHA-2算法概述

2.4.1. 消息填充

在 SHA 系列哈希算法中,消息填充是将输入消息调整到特定长度的过程,以便满足算法处理的要求。

消息填充模 512 与 448 同余:

        (1)SHA-256 和 SHA-512 都采用类似的填充机制,以确保输入消息的长度在处理之前符合特定要求。

        (2)消息的长度在被填充后,需要满足以下条件:

                   填充后的消息长度要对 512 取模,且填充后的长度要为 448 位,即填充后的消息长度为 𝐿 ≡ 448 mod  512。

                  填充过程一般是先添加一个 "1" 位(0x80),然后添加足够的 "0" 位,使得消息长度(以比特为单位)达到 448 位。最后,附加一个 64 位的表示原始消息长度的值,确保最终的消息长度是 512 位的整数倍。

2.4.2 初始化链接变量缓冲区(SHA-256)

SHA-256 初始化时使用 8 个 32 位的寄存器,这些寄存器通常称为链值(chain values),用于存储哈希计算的中间结果。

链接变量:

这些链接变量的初始值取自前 8 个素数的平方根的小数部分,具体是:2、3、5、7、11、13、17、19 的平方根小数部分的前 32 位二进制表示。

初始值为:

  • h0​=6a09e667
  • ℎ1=𝑏𝑏67𝑎𝑒85
  • ℎ2=3𝑐6𝑒𝑓372
  • ℎ3=𝑎54𝑓𝑓53𝑎
  • ℎ4=510𝑒527𝑓
  • ℎ5=9𝑏05688𝑐
  • ℎ6=1𝑓83𝑑9𝑎𝑏
  • ℎ7=5𝑏𝑒0𝑐𝑑19

2.4.3 SHA-512 处理

SHA-512 和 SHA-256 相似,但使用的是 64 位的寄存器并以 1024 位(128 字节)为一个分组进行处理。

初始化链接变量:

SHA-512 使用 8 个 64 位的寄存器,初始化值取自前 8 个素数的平方根小数部分的前 64 位二进制表示。

分组和循环:

SHA-512 将输入消息分成 1024 位的块进行处理,每个块经过 80 轮的迭代(与 SHA-256 的 64 轮相比,SHA-512 具有更多的迭代)。

每一轮都使用特定的计算函数(如选择、置换等)和常数值,对每个分组进行复杂的运算。

2.4.4 SHA-384 和 SHA-512 的迭代函数

SHA-384 和 SHA-512 虽然在位数和寄存器大小上有所不同,但它们也都使用了 6 个迭代函数来处理输入块。

  • Ch(选择):根据条件选择输入位。
  • Maj(多数):计算三个输入位的多数。
  • Σ0 和 Σ1:旋转和位移操作,用于产生混淆和扩散。
  • σ0 和 σ1:类似于 Σ,但在处理中使用不同的旋转和位移位数。

 三、模拟比特币挖矿

3.1 区块的结构

一个区块包含多个字段,每个字段在挖矿过程中都有特定的作用:

(1)版本(version):表示区块的版本号,提供了协议的版本信息,可能影响区块的创建或验证方式。

(2)上一个区块的哈希值(prev_hash):这是前一个区块的 SHA-256 哈希值,用于连接区块链。确保每个区块都有效地链接到前一个区块,形成不可篡改的链条。

(3)交易记录的哈希树值(merkle_root):这是当前区块中所有交易的 Merkle 树根哈希,提供了一种高效验证区块内所有交易是否被篡改的方式。

(4)更新时间(ntime):该字段记录当前区块创建的时间戳,通常以 Unix 时间戳表示。

(5)当前难度(nbits):该字段表示目标的难度级别,影响挖矿时需要找到的哈希值的范围。

3.2. 挖矿过程

挖矿过程的核心是找到一个有效的 nonce 值,使得生成的区块哈希满足特定的难度目标。

1. 构造区块头:

区块头由以下字段组成:

  • version
  • prev_hash
  • merkle_root
  • ntime
  • nbits
  • nonce(初始值为 0)

2. 计算哈希:

        使用 SHA-256 两次对区块头进行哈希计算:
           

        这里的 `||` 表示将各个字段串联在一起。

3. 比较哈希与目标:

        计算出的哈希值需要与一个预定义的目标(TARGET)进行比较。

        如果计算出的哈希值小于或等于 TARGET,则该 nonce 是有效的,矿工成功找到一个有效的区块。

4. 调整难度:

        TARGET 由当前难度决定,难度会根据网络的算力变化而调整,以确保新区块的生成时间大约为 10 分钟。TARGET 的计算公式通常与 nbits 相关。

3.3 TARGET 的计算

TARGET 可以根据当前的难度 (nbits) 计算得出:

        nbits 是一个表示难度的 32 位数字,通常以十六进制表示,前面的字节用于表示目标的位数(例如,00000000),后面的部分表示目标值的具体大小(例如,FFFF)。

        计算出的 TARGET 值越小,挖矿难度越高。

比特币挖矿的过程涉及到多个关键的区块字段和复杂的哈希计算,以确保区块链的安全性和完整性。矿工通过不断尝试不同的 nonce 值,来寻找满足当前难度目标的有效哈希值,以便成功添加新区块到区块链中。

3.4 源码演示

#include <iostream> // 引入输入输出流库,以便使用 cout 和 cerr
#include <openssl/md5.h> // 引入 OpenSSL 的 MD5 相关函数
#include <openssl/sha.h> // 引入 OpenSSL 的 SHA 相关函数
#include <fstream> // 引入文件流库,以便读取文件
#include <iomanip> // 引入用于格式化输入输出的库,例如设置输出宽度
#include <thread> // 引入线程库,以便使用睡眠功能
#include <vector> // 引入 STL 的 vector 容器
#include <cstring> // 引入 C 字符串库,以便使用 strlen 和 memcpy

using namespace std; // 使用标准命名空间,方便后续使用标准库中的元素

void TestBit() // 定义 TestBit 函数
{
    unsigned char data[128] = "测试比特币挖矿,模拟交易链"; // 定义一个字符数组并初始化为测试字符串
    unsigned int nonce = 0; // 定义 nonce 变量并初始化为 0,用于寻找有效 nonce
    int data_size = strlen((char*)data); // 获取数据大小,返回字符串的长度
    unsigned char md1[1024] = { 0 }; // 定义 md1 数组用于存储第一次 SHA-256 哈希结果,初始化为 0
    unsigned char md2[1024] = { 0 }; // 定义 md2 数组用于存储第二次 SHA-256 哈希结果,初始化为 0
    for (;;) // 无限循环,直到找到满足条件的 nonce
    {
        nonce++; // 增加 nonce 值
        memcpy(data + data_size, &nonce, sizeof(nonce)); // 将当前 nonce 值复制到 data 数组的末尾
        SHA256(data, data_size + sizeof(nonce), md1); // 计算 data 的 SHA-256 哈希,结果存入 md1
        SHA256(md1, 64, md2); // 计算 md1 的 SHA-256 哈希,结果存入 md2
        //工作量的难度
        if (md2[0] == 0) break; // 如果 md2 的第一个字节为 0,则跳出循环
    }
    cout << "nonce = " << nonce << endl; // 输出找到的 nonce 值
}

int main() { // 主函数入口
    cout << "Test Bit" << endl; // 输出测试信息
    TestBit(); // 调用 TestBit 函数

    return 0; // 程序正常结束
}

运行效果:

当我们增加难度以后:

if (md2[0] == 0) break;//原代码难度

if (md2[0] == 0 && md2[1] == 0) break;//修改代码难度

 再次运行效果:

 3.5 算法思路

1. 目标定义:

挖矿的目标是找到一个满足特定条件的哈希值。在比特币网络中,通常是使得哈希值的前面若干位为零。这个条件可以通过调整 nonce 值来实现。

2. 数据准备:

挖矿过程中,矿工需要准备一个数据块这个数据块通常包含交易信息、时间戳、前一个区块的哈希值等。在这里,代码使用了一个简单的字符串 "测试比特币挖矿,模拟交易链" 作为基础数据。

nonce 是一个用于生成哈希值的随机数,它会随着每次尝试而变化。

3. 哈希计算:

使用 SHA-256 哈希算法对数据进行多次哈希计算。在每次尝试中,程序将当前的 nonce 值附加到数据块中,然后计算哈希值。

具体流程如下:

        (1)计算当前数据块的哈希(SHA256(data)),这个哈希值存储在 md1 中。

        (2)再对 md1 进行一次哈希(SHA256(md1)),结果存储在 md2 中。这个双重哈希的目的是增加安全性和复杂性。

4. 有效性检查:

在每次哈希计算后,检查 md2 的第一个字节是否为 0。如果满足条件,说明找到了符合挖矿要求的哈希值,程序就可以停止查找。

5. 输出结果:

一旦找到了有效的 nonce,程序会将其打印输出。这告诉矿工他们成功地找到了一个有效的哈希。

总结:

  • 初始化: 准备数据和 nonce,计算数据的初始大小。
  • 循环尝试: 通过无限循环,不断增加 nonce 值,更新数据,计算哈希。
  • 哈希计算: 每次尝试都计算两次哈希值,增加难度和安全性。
  • 条件检查: 检查计算出的哈希值是否满足特定条件以决定是否继续。
  • 结果输出: 找到有效 nonce 后打印结果。
  • 暴力搜索: 这个算法实际上是一种暴力搜索策略。通过不断尝试不同的 nonce 值,直到找到一个合适的哈希值。这是挖矿过程中常见的做法。
  • 安全性和复杂性: 通过双重哈希计算,增加了找到哈希的复杂性和安全性,防止简单的逆向工程和破解。
  • 分布式系统特性: 由于比特币网络是去中心化的,许多矿工在全球范围内同时进行这个过程,从而竞争解决问题并获得奖励。

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

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

相关文章

【ESP32S3学习笔记】与有人AP520X路由器连接失败的问题

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 项目上新换了个路由器&#xff0c;结果发现ESP32模组连接不上&#xff0c;其他的路由器都正常。 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 对比log发现有问题的时候&#x…

智慧大棚数据库版

创建一个SMartBigHouse数据库 在数据库创建一个表用来存储数据 这边将id设为主键并将标识增量设为1 搭建Winfrom 搭建历史查询界面 串口数据&#xff0c;(这边是用的一个虚拟的串口工具&#xff0c;需要的话私) ModbusSerialMaster master;DataPointCollection wenduValues; //…

Win10使用VS Code远程连接Ubuntu服务器时遇到SSH公钥错误的解决方案

在使用Windows 10上的Visual Studio Code&#xff08;VS Code&#xff09;远程连接Ubuntu 20.04服务器时&#xff0c;遇到了以下错误&#xff1a; 错误的原因 这个错误消息表明&#xff0c;SSH 客户端检测到远程主机的 ECDSA 公钥已更改。可能是由于以下原因之一&#xff1a…

python—NumPy的基础(2)

文章目录 一维数组索引和切片一维数组索引和切片的使用一维数组负索引和切片的使用 二维数组的索引和切片索引直接获取使用坐标获取数组[x,y]二维数组负索引的使用切片数组的复制 改变数组的维度改变数组的维度 数组的拼接列表的拼接一维数组的拼接二维数组的拼接vstack 与hsta…

el-image预览图片点击遮盖处关闭预览

预览关闭按钮不明显 解决方式&#xff1a; 1.修改按钮样式明显点&#xff1a; //el-image 添加自定义类名&#xff0c;下文【test-image】代指 .test-image .el-icon-circle-close{ color:#fff; font-size:20px; ...改成很明显的样式 }2.使用事件监听&#xff0c;监听当前遮…

第十一章 数据结构

第十一章 数据结构 11.1 数组 数组是元素的顺序集合&#xff0c;通常这些元素具有相同的数据类型 索引表示元素在数组中的顺序号&#xff0c;顺序号从数组开始处计数 数组元素通过索引被独立给出了地址&#xff0c;数组整体上有一个名称&#xff0c;但每个元素利用数组的的…

TCP网络socket编程(面向连接)

Tcp面向链接、面向字节流和文件的读写非常类似&#xff08;&#xff09;&#xff1a;客户端创建套接字主动建立连接&#xff0c;服务器监听套接字一直等待连接的到来&#xff0c;监听到一个&#xff0c;就创建一个新的套接字用于IO 服务器&#xff1a; 创建套接字&#xff1a…

区块链和数据要素融合的价值及应用

一、数据要素面临的关键障碍 在构建数据要素基石的过程中&#xff0c;首要任务是明确并解决产权架构的难题&#xff0c;特别是使用权的确立与流转机制的顺畅&#xff0c;此乃数字经济蓬勃发展的命脉所在。一个高效的数据流转体系对于激发数据潜能、加速经济发展及优化数据资源…

TreeSelect增加可筛选功能

TreeSelect官方可筛选示例 <template><el-tree-selectv-model"value":data"data"filterablestyle"width: 240px"/><el-divider /><el-divider />filter node method:<el-tree-selectv-model"value":data&q…

使用torch_pruning对YOLOv8进行剪枝(新版、detect、segment)

torch_pruning库介绍 在结构修剪中&#xff0c;**Group被定义为深度网络中最小的可移除单元。**每个组由多个相互依赖的层组成&#xff0c;需要同时修剪这些层以保持最终结构的完整性。然而&#xff0c;深度网络通常表现出层与层之间错综复杂的依赖关系&#xff0c;这对结构修剪…

[大牛直播SDK]Windows平台RTMP直播推送模块功能设计

技术优势 全自研框架&#xff0c;易于扩展&#xff0c;自适应算法让延迟更低、采集编码传输效率更高&#xff1b;所有功能以SDK接口形式提供&#xff0c;所有状态&#xff0c;均有event回调&#xff0c;完美支持断网自动重连&#xff1b;SDK模块化&#xff0c;可和大牛直播播放…

DBeaver Ultimate 22.1.0 连接数据库(MySQL+Mongo+Clickhouse)

前言 继续书接上文 Docker Compose V2 安装常用数据库MySQL+Mongo,部署安装好之后我本来是找了一个web端的在线连接数据库的工具,但是使用过程中并不丝滑,最终还是选择了使用 DBeaver ,然后发现 mongo 还需要许可,又折腾整理了半下午,终于大功告成。 DBeaver 版本及说明…

SpringBoot集成Sharding-JDBC实现分库分表

本文已收录于专栏 《中间件合集》 目录 版本介绍背景介绍拆分方式集成并测试1.引入依赖2.创建库和表3.pom文件配置3.编写测试类Entity层Mapper接口MapperXML文件测试类 4.运行结果 自定义分片规则定义分片类编写pom文件 总结提升 版本介绍 SpringBoot的版本是&#xff1a; 2.3.…

SpringBoot上传超大文件导致Cannot read more than 2,147,483,647 into a byte array,问题解决办法

问题描述 报错: java.lang.IllegalArgumentException: Cannot read more than 2,147,483,647 into a byte array at org.apache.commons.io.IOUtils.lambda$toByteArray$0(IOUtils.java:2403) ~[commons-io-2.11.0.jar:2.11.0] at org.apache.commons.io.output.Thre…

python每日学习12:pandas库的用法(1)

python每日学习12&#xff1a;pandas库的用法&#xff08;1&#xff09; 安装 pip install pandas设定系统环境 import pandas as pd #设定自由列表输出最多为 10 行 pd.options.display.max_rows 10 # 显示当前 Pandas 版本号 pd.__version__进入jupyter notebook 页面 在终端…

氧气传感器在汽车制氧检测中的应用

在当今汽车工业中&#xff0c;技术的快速发展不仅带来了驾驶安全性和舒适性的显著提升&#xff0c;还为车辆增加了各种智能功能&#xff0c;以应对不同的驾驶环境和需求。氧气传感器作为一种关键的技术装置&#xff0c;在汽车制氧检测系统中的应用&#xff0c;尤其是针对疲劳驾…

困于数字化泥潭的软件公司|专题报告集

数字化专题报告集链接&#xff1a;https://tecdat.cn/?p36964 在探讨企业数字化转型的进程中&#xff0c;软件公司无疑扮演着举足轻重的角色。它们不仅是技术创新的驱动力&#xff0c;更是连接管理与技术的桥梁。然而&#xff0c;正如许多观察家所指出的那样&#xff0c;软件…

每天五分钟计算机视觉:目标检测模型从RCNN到Fast R-CNN的进化

本文重点 前面的课程中,我们学习了RCNN算法,但是RCNN算法有些慢,然后又有了基于RCNN的Fast-RCNN,Fast R-CNN是一种深度学习模型,主要用于目标检测任务,尤其在图像中物体的识别和定位方面表现出色。它是R-CNN系列算法的一个重要改进版本,旨在解决R-CNN中计算量大、速度慢…

jackson序列化(jackson codec)

Jackson 是一个用于 Java 平台的开源 JSON 库&#xff0c;它提供了灵活且高效的方式来处理 JSON 数据的序列化(Java对象 → JSON字符串)和反序列化(JSON 字符串→ Java对象)。 以下是 Jackson 的一些主要特点和功能&#xff1a; 高性能&#xff1a;Jackson 通过使用基于流的处理…

Ubuntu安装QQ教程

Ubuntu安装QQ教程 腾讯更新Linux版的QQ&#xff0c;这里安装一下&#xff1b; 首先&#xff0c;进入官网找到合适对应的安装包&#xff1b; QQLinux版本官网&#xff1a;https://im.qq.com/linuxqq/index.shtml 我们是ubuntu系统选择X86下的deb版本&#xff0c;如果是arm开…