C 多线程实现大文件固定大小分卷与 SHA256 哈希校验

在日常开发中,我们经常会遇到需要处理大文件的场景,比如将大文件分割成固定大小的分卷以便传输或存储,同时为了保证文件的完整性,还需要对每个分卷进行哈希校验。本文将介绍如何使用 C语言 多线程技术实现这一功能,结合 OpenSSL 库计算 SHA256 哈希值,提高处理效率。

目录

功能概述

技术要点

核心函数解析

[1. 核心数据结构](#1. 核心数据结构)

[2. 计时函数:getCurrentTime()](#2. 计时函数:getCurrentTime())

[3. SHA256计算函数:calculate_sha256()](#3. SHA256计算函数:calculate_sha256())

[4. 获取文件大小函数:get_file_size()](#4. 获取文件大小函数:get_file_size())

[5. 线程处理函数:process_splits()](#5. 线程处理函数:process_splits())

[6. 主处理函数:split_file_fixed_size()](#6. 主处理函数:split_file_fixed_size())

[7. 主函数:main()](#7. 主函数:main())

完整代码示例

使用说明

性能优化

扩展建议

总结


功能概述

本程序实现的核心功能包括:

  • 将指定文件按照固定大小(可配置)分割为多个分卷
  • 使用多线程并行处理分卷任务,提高处理速度
  • 为每个分卷计算 SHA256 哈希值,并将分卷命名为 "分卷序号_哈希值.txt"
  • 记录每个分卷的哈希值、大小和处理线程 ID 到日志文件
  • 计算并记录完整文件的 SHA256 哈希值,用于最终校验

技术要点

  • 多线程编程:使用 pthread 库创建和管理线程,实现并行处理
  • 文件操作:精确控制文件指针,实现分卷读取和写入
  • 哈希计算:使用 OpenSSL 库的 SHA256 算法计算文件哈希值
  • 线程同步:使用互斥锁(mutex)保证多线程对日志文件的安全写入
  • 任务分配:将分卷任务均匀分配给各个线程,提高处理效率

核心函数解析

1. 核心数据结构

cs 复制代码
// 线程参数结构体
typedef struct
{
    const char *input_file;      // 输入文件路径
    long split_size;             // 固定分卷大小(SPLIT_SIZE)
    int start_split;             // 线程负责的起始分卷序号
    int end_split;               // 线程负责的结束分卷序号(不含)
    int thread_id;               // 线程ID
    FILE *hash_file;             // 哈希记录文件指针
    pthread_mutex_t *hash_mutex; // 哈希写入互斥锁
} ThreadArgs;

2. 计时函数:getCurrentTime()

cs 复制代码
double getCurrentTime() {
    struct timeval tv;
    gettimeofday(&tv, NULL); // 获取高精度时间
    return tv.tv_sec + tv.tv_usec / 1000000.0;
}

功能:获取当前精确时间(秒级+微秒级)

实现细节

  • 使用gettimeofday()系统调用获取时间

  • 将秒和微秒合并为双精度浮点数返回

  • 用于性能测试和耗时统计

3. SHA256计算函数:calculate_sha256()

cs 复制代码
char *calculate_sha256(const unsigned char *buffer, size_t length) {
    unsigned char digest[SHA256_DIGEST_LENGTH];
    SHA256(buffer, length, digest);

    char *hash_str = (char *)malloc(SHA256_DIGEST_LENGTH * 2 + 1);
    if (!hash_str) {
        perror("哈希字符串内存分配失败");
        return NULL;
    }

    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(hash_str + i * 2, "%02x", digest[i]);
    }
    hash_str[SHA256_DIGEST_LENGTH * 2] = '\0';
    return hash_str;
}

功能:计算给定缓冲区的SHA256哈希值

参数

  • buffer: 输入数据缓冲区

  • length: 数据长度

实现细节

  • 使用OpenSSL的SHA256函数计算哈希

  • 将二进制哈希值转换为十六进制字符串

  • 动态分配内存存储哈希字符串

  • 返回的字符串需要调用者手动释放

4. 获取文件大小函数:get_file_size()

cs 复制代码
long get_file_size(FILE *fp) {
    if (!fp)
        return -1;
    long current_pos = ftell(fp);
    fseek(fp, 0L, SEEK_END);
    long size = ftell(fp);
    fseek(fp, current_pos, SEEK_SET);
    return size;
}

功能:获取打开文件的大小

参数

  • fp: 已打开的文件指针

实现细节

  • 保存当前文件位置

  • 移动到文件末尾获取大小

  • 恢复原始文件位置

  • 返回文件大小(字节数)

5. 线程处理函数:process_splits()

cs 复制代码
void *process_splits(void *arg) {
    ThreadArgs *args = (ThreadArgs *)arg;
    // 打开输入文件
    FILE *in_fp = fopen(args->input_file, "rb");
    // 分配缓冲区
    unsigned char *buffer = (unsigned char *)malloc(args->split_size);
    
    // 处理分配的分卷范围
    for (int split_idx = args->start_split; split_idx < args->end_split; split_idx++) {
        // 定位分卷位置
        fseek(in_fp, (long)split_idx * args->split_size, SEEK_SET);
        // 读取分卷数据
        size_t bytes_read = fread(buffer, 1, args->split_size, in_fp);
        // 计算哈希
        char *hash_str = calculate_sha256(buffer, bytes_read);
        // 创建分卷文件
        char output_filename[256];
        snprintf(output_filename, sizeof(output_filename),
                 "split_%d_%s.txt", split_idx, hash_str);
        FILE *out_fp = fopen(output_filename, "wb");
        // 写入分卷数据
        fwrite(buffer, 1, bytes_read, out_fp);
        fclose(out_fp);
        
        // 线程安全写入哈希记录
        pthread_mutex_lock(args->hash_mutex);
        fprintf(args->hash_file, "分卷%d:%s(大小:%zu字节,线程%d)\n",
                split_idx, hash_str, bytes_read, args->thread_id);
        pthread_mutex_unlock(args->hash_mutex);
        
        free(hash_str);
    }
    
    // 释放资源
    free(buffer);
    fclose(in_fp);
    pthread_exit(NULL);
}

参数

  • arg: 包含线程参数的ThreadArgs结构体指针

实现细节

  • 每个线程独立打开输入文件,避免文件指针冲突

  • 处理指定范围内的分卷(从start_split到end_split-1)

  • 对每个分卷:

    • 定位到正确位置

    • 读取数据

    • 计算SHA256哈希

    • 创建分卷文件(命名格式:split_序号_哈希值.txt)

    • 写入数据

  • 使用互斥锁保证哈希记录的线程安全

  • 合理释放所有分配的资源

6. 主处理函数:split_file_fixed_size()

cs 复制代码
void split_file_fixed_size(const char *input_file, long split_size, int thread_count) {
    // 打开输入文件并获取大小
    FILE *in_fp = fopen(input_file, "rb");
    long file_size = get_file_size(in_fp);
    
    // 计算总分卷数
    int total_splits = (file_size + split_size - 1) / split_size;
    
    // 创建哈希记录文件
    char hash_filename[256];
    snprintf(hash_filename, sizeof(hash_filename), "m_%d.txt", thread_count);
    FILE *hash_file = fopen(hash_filename, "w");
    
    // 初始化线程和互斥锁
    pthread_t *threads = (pthread_t *)malloc(thread_count * sizeof(pthread_t));
    ThreadArgs *args = (ThreadArgs *)malloc(thread_count * sizeof(ThreadArgs));
    pthread_mutex_t hash_mutex;
    pthread_mutex_init(&hash_mutex, NULL);
    
    // 分配线程任务
    int splits_per_thread = total_splits / thread_count;
    int remaining_splits = total_splits % thread_count;
    int current_split = 0;
    
    for (int i = 0; i < thread_count; i++) {
        args[i].input_file = input_file;
        args[i].split_size = split_size;
        args[i].start_split = current_split;
        args[i].end_split = current_split + splits_per_thread + (i < remaining_splits ? 1 : 0);
        args[i].thread_id = i;
        args[i].hash_file = hash_file;
        args[i].hash_mutex = &hash_mutex;
        
        current_split = args[i].end_split;
        pthread_create(&threads[i], NULL, process_splits, &args[i]);
    }
    
    // 等待线程完成
    for (int i = 0; i < thread_count; i++)
        pthread_join(threads[i], NULL);
    
    // 计算完整文件哈希
    rewind(in_fp);
    SHA256_CTX full_ctx;
    SHA256_Init(&full_ctx);
    unsigned char buf[8192];
    size_t bytes;
    while ((bytes = fread(buf, 1, sizeof(buf), in_fp)) > 0) {
        SHA256_Update(&full_ctx, buf, bytes);
    }
    unsigned char full_digest[SHA256_DIGEST_LENGTH];
    SHA256_Final(full_digest, &full_ctx);
    char full_hash[SHA256_DIGEST_LENGTH * 2 + 1];
    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(full_hash + i * 2, "%02x", full_digest[i]);
    }
    fprintf(hash_file, "完整文件哈希: %s\n", full_hash);
    
    // 释放资源
    free(threads);
    free(args);
    pthread_mutex_destroy(&hash_mutex);
    fclose(hash_file);
    fclose(in_fp);
}

功能:主处理函数,组织多线程分卷处理

参数

  • input_file: 输入文件路径

  • split_size: 每个分卷的大小

  • thread_count: 使用的线程数

实现细节

  1. 打开输入文件并验证

  2. 计算总分卷数(向上取整)

  3. 创建哈希记录文件(文件名包含线程数)

  4. 初始化线程和互斥锁

  5. 均匀分配分卷给各线程(余数分给前几个线程)

  6. 创建并启动线程

  7. 等待所有线程完成

  8. 计算完整文件的SHA256哈希并记录

  9. 释放所有资源

7. 主函数:main()

cs 复制代码
int main(int argc, char const *argv[]) {
    const char *input_file = "mvn.cmd";
    int thread_counts[] = {4, 8}; // 测试不同线程数
    
    // 处理命令行参数
    if (argc >= 2) input_file = argv[1];
    if (argc >= 3) {
        thread_counts[0] = atoi(argv[2]);
        // 只使用命令行指定的线程数
    }
    
    // 测试不同线程数
    for (int i = 0; i < sizeof(thread_counts)/sizeof(thread_counts[0]); i++) {
        int thread_count = thread_counts[i];
        printf("\n====== 测试线程数: %d ======\n", thread_count);
        double startTime = getCurrentTime();
        split_file_fixed_size(input_file, SPLIT_SIZE, thread_count);
        double endTime = getCurrentTime();
        printf("耗时:%.3f秒\n", endTime - startTime);
    }
    return EXIT_SUCCESS;
}

功能:程序入口,处理命令行参数并测试不同线程数

实现细节

  • 默认输入文件和线程数

  • 支持命令行参数指定输入文件和线程数

  • 对每种线程数配置:

    • 记录开始时间

    • 调用主处理函数

    • 记录结束时间

    • 输出耗时

完整代码示例

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <openssl/sha.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#define SPLIT_SIZE (1024 * 1) // 固定分卷大小(1KB,可修改)

// 声明计时函数,解决函数声明顺序问题
double getCurrentTime();

// 线程参数结构体
typedef struct
{
    const char *input_file;      // 输入文件路径
    long split_size;             // 固定分卷大小(SPLIT_SIZE)
    int start_split;             // 线程负责的起始分卷序号
    int end_split;               // 线程负责的结束分卷序号(不含)
    int thread_id;               // 线程ID
    FILE *hash_file;             // 哈希记录文件指针
    pthread_mutex_t *hash_mutex; // 哈希写入互斥锁
} ThreadArgs;

// 计算SHA256哈希值
char *calculate_sha256(const unsigned char *buffer, size_t length)
{
    unsigned char digest[SHA256_DIGEST_LENGTH];
    SHA256(buffer, length, digest);

    char *hash_str = (char *)malloc(SHA256_DIGEST_LENGTH * 2 + 1);
    if (!hash_str)
    {
        perror("哈希字符串内存分配失败");
        return NULL;
    }

    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
    {
        sprintf(hash_str + i * 2, "%02x", digest[i]);
    }
    hash_str[SHA256_DIGEST_LENGTH * 2] = '\0';
    return hash_str;
}

// 获取文件大小
long get_file_size(FILE *fp)
{
    if (!fp)
        return -1;
    long current_pos = ftell(fp);
    fseek(fp, 0L, SEEK_END);
    long size = ftell(fp);
    fseek(fp, current_pos, SEEK_SET);
    return size;
}

// 线程处理函数:处理分配的分卷,每个分卷大小为SPLIT_SIZE
void *process_splits(void *arg)
{
    ThreadArgs *args = (ThreadArgs *)arg;

    // 打开输入文件(每个线程独立打开,避免指针冲突)
    FILE *in_fp = fopen(args->input_file, "rb");
    if (!in_fp)
    {
        fprintf(stderr, "线程%d:无法打开输入文件 %s: %s\n",
                args->thread_id, args->input_file, strerror(errno));
        pthread_exit(NULL);
    }

    // 分配固定大小的缓冲区(与分卷大小一致)
    unsigned char *buffer = (unsigned char *)malloc(args->split_size);
    if (!buffer)
    {
        perror("线程缓冲区分配失败");
        fclose(in_fp);
        pthread_exit(NULL);
    }

    // 处理当前线程负责的所有分卷(从start_split到end_split-1)
    for (int split_idx = args->start_split; split_idx < args->end_split; split_idx++)
    {
        // 计算当前分卷在文件中的起始偏移量
        long file_offset = (long)split_idx * args->split_size;

        // 移动文件指针到分卷起始位置
        if (fseek(in_fp, file_offset, SEEK_SET) != 0)
        {
            fprintf(stderr, "线程%d:分卷%d定位失败: %s\n",
                    args->thread_id, split_idx, strerror(errno));
            continue;
        }

        // 读取固定大小的数据(最后一个分卷可能小于SPLIT_SIZE)
        size_t bytes_read = fread(buffer, 1, args->split_size, in_fp);
        if (bytes_read == 0)
        {
            if (ferror(in_fp))
                perror("线程读取文件错误");
            break;
        }

        // 计算当前分卷的哈希值
        char *hash_str = calculate_sha256(buffer, bytes_read);
        if (!hash_str)
        {
            fprintf(stderr, "线程%d:分卷%d哈希计算失败,跳过\n",
                    args->thread_id, split_idx);
            continue;
        }

        // 构造分卷文件名:全局分卷序号_哈希值.txt
        char output_filename[256];
        snprintf(output_filename, sizeof(output_filename),
                 "split_%d_%s.txt", split_idx, hash_str);

        // 以写入模式创建分卷文件(确保每次都是新文件)
        FILE *out_fp = fopen(output_filename, "wb");
        if (!out_fp)
        {
            fprintf(stderr, "线程%d:无法创建分卷%d: %s\n",
                    args->thread_id, split_idx, strerror(errno));
            free(hash_str);
            continue;
        }

        // 写入分卷数据(严格按读取的字节数写入)
        size_t bytes_written = fwrite(buffer, 1, bytes_read, out_fp);
        fclose(out_fp);

        if (bytes_written != bytes_read)
        {
            fprintf(stderr, "线程%d:分卷%d写入不完整(%zu/%zu字节)\n",
                    args->thread_id, split_idx, bytes_written, bytes_read);
            remove(output_filename); // 删除无效文件
            free(hash_str);
            continue;
        }

        // 线程安全记录哈希值到文件
        pthread_mutex_lock(args->hash_mutex);
        fprintf(args->hash_file, "分卷%d:%s(大小:%zu字节,线程%d)\n",
                split_idx, hash_str, bytes_written, args->thread_id);
        pthread_mutex_unlock(args->hash_mutex);

        free(hash_str); // 释放哈希字符串内存
    }

    // 清理资源
    free(buffer);
    fclose(in_fp);
    pthread_exit(NULL);
}

// 多线程固定大小分片函数
void split_file_fixed_size(const char *input_file, long split_size, int thread_count)
{
    // 验证分卷大小合法性
    if (split_size <= 0)
    {
        fprintf(stderr, "错误:分卷大小必须为正整数\n");
        exit(EXIT_FAILURE);
    }

    // 打开输入文件并获取大小
    FILE *in_fp = fopen(input_file, "rb");
    if (!in_fp)
    {
        perror("无法打开输入文件");
        exit(EXIT_FAILURE);
    }
    long file_size = get_file_size(in_fp);
    if (file_size <= 0)
    {
        fprintf(stderr, "无效文件大小:%ld\n", file_size);
        fclose(in_fp);
        exit(EXIT_FAILURE);
    }
    printf("输入文件:%s,大小:%ld字节,固定分卷大小:%ld字节\n",
           input_file, file_size, split_size);

    // 计算总分卷数(向上取整)
    int total_splits = (file_size + split_size - 1) / split_size;
    printf("固定分卷总数:%d,使用%d个线程处理\n", total_splits, thread_count);

    // 为每个线程创建独立的哈希记录文件(如m_4.txt、m_8.txt)
    char hash_filename[256];
    snprintf(hash_filename, sizeof(hash_filename), "m_%d.txt", thread_count);
    FILE *hash_file = fopen(hash_filename, "w");
    if (!hash_file)
    {
        perror("无法创建哈希记录文件");
        fclose(in_fp);
        exit(EXIT_FAILURE);
    }

    // 初始化线程和互斥锁
    pthread_t *threads = (pthread_t *)malloc(thread_count * sizeof(pthread_t));
    ThreadArgs *args = (ThreadArgs *)malloc(thread_count * sizeof(ThreadArgs));
    pthread_mutex_t hash_mutex;
    pthread_mutex_init(&hash_mutex, NULL);

    if (!threads || !args)
    {
        perror("线程资源分配失败");
        exit(EXIT_FAILURE);
    }

    // 计算每个线程处理的分卷数(均匀分配)
    int splits_per_thread = total_splits / thread_count;
    int remaining_splits = total_splits % thread_count;

    // 分配线程任务
    int current_split = 0; // 当前分卷序号
    for (int i = 0; i < thread_count; i++)
    {
        args[i].input_file = input_file;
        args[i].split_size = split_size;
        args[i].start_split = current_split;
        // 前remaining_splits个线程多处理1个分卷,确保分配均匀
        args[i].end_split = current_split + splits_per_thread + (i < remaining_splits ? 1 : 0);
        args[i].thread_id = i;
        args[i].hash_file = hash_file;
        args[i].hash_mutex = &hash_mutex;

        // 更新当前分卷序号
        current_split = args[i].end_split;

        // 创建线程
        int ret = pthread_create(&threads[i], NULL, process_splits, &args[i]);
        if (ret != 0)
        {
            fprintf(stderr, "创建线程%d失败: %s\n", i, strerror(ret));
            // 清理已创建的线程
            for (int j = 0; j < i; j++)
                pthread_join(threads[j], NULL);
            free(threads);
            free(args);
            fclose(hash_file);
            fclose(in_fp);
            pthread_mutex_destroy(&hash_mutex);
            exit(EXIT_FAILURE);
        }
    }

    // 等待所有线程完成
    for (int i = 0; i < thread_count; i++)
        pthread_join(threads[i], NULL);

    // 计算并记录完整文件的哈希值(验证用)
    rewind(in_fp);
    SHA256_CTX full_ctx;
    SHA256_Init(&full_ctx);
    unsigned char buf[8192];
    size_t bytes;

    while ((bytes = fread(buf, 1, sizeof(buf), in_fp)) > 0)
    {
        SHA256_Update(&full_ctx, buf, bytes);
    }

    unsigned char full_digest[SHA256_DIGEST_LENGTH];
    SHA256_Final(full_digest, &full_ctx);
    char full_hash[SHA256_DIGEST_LENGTH * 2 + 1];

    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
    {
        sprintf(full_hash + i * 2, "%02x", full_digest[i]);
    }
    fprintf(hash_file, "完整文件哈希: %s\n", full_hash);

    // 清理资源
    free(threads);
    free(args);
    pthread_mutex_destroy(&hash_mutex);
    fclose(hash_file);
    fclose(in_fp);
    printf("固定大小分卷完成,哈希记录已保存到 %s\n", hash_filename);
}

// 实现计时函数
double getCurrentTime()
{
    struct timeval tv;
    gettimeofday(&tv, NULL); // 获取高精度时间
    return tv.tv_sec + tv.tv_usec / 1000000.0;
}

int main(int argc, char const *argv[])
{
    const char *input_file = "mvn.cmd";
    int thread_counts[] = {4, 8}; // 测试不同线程数
    int count = sizeof(thread_counts) / sizeof(thread_counts[0]);

    // 处理命令行参数
    if (argc >= 2)
        input_file = argv[1];
    if (argc >= 3)
    {
        thread_counts[0] = atoi(argv[2]);
        count = 1; // 只使用命令行指定的线程数
    }

    // 测试不同线程数
    for (int i = 0; i < count; i++)
    {
        int thread_count = thread_counts[i];
        if (thread_count <= 0)
        {
            fprintf(stderr, "跳过无效线程数: %d\n", thread_count);
            continue;
        }
        printf("\n====== 测试线程数: %d(固定分卷大小:%ld字节)======\n",
               thread_count, (long)SPLIT_SIZE);
        double startTime = getCurrentTime();
        split_file_fixed_size(input_file, SPLIT_SIZE, thread_count);
        double endTime = getCurrentTime();
        double duration = endTime - startTime;
        printf("耗时:%.3f秒\n", duration);
    }

    return EXIT_SUCCESS;
}

使用说明

编译:需要链接OpenSSL和pthread库

cs 复制代码
gcc program.c -o splitter -lssl -lcrypto -lpthread

运行:

cs 复制代码
./splitter [输入文件] [线程数]

输出:

  • 多个分卷文件(split_序号_哈希值.txt)

  • 哈希记录文件(m_线程数.txt)

性能优化

  • 每个线程独立打开输入文件,避免文件指针竞争

  • 使用互斥锁保护共享资源(哈希记录文件)

  • 动态内存分配最小化

  • 均匀分配任务给各线程

扩展建议

  • 添加进度显示功能

  • 支持可变分卷大小

  • 添加恢复/校验功能

  • 支持更多哈希算法

总结

这个多线程文件分片和哈希计算的实现,展示了如何在C语言中使用 pthreads 库进行并行编程,以提高大文件处理的效率。它涵盖了以下关键概念:

  • 线程创建与管理: pthread_create 和 pthread_join。

  • 线程间数据共享与同步: 使用互斥锁 (pthread_mutex_t) 保护共享资源(哈希记录文件)的访问。

  • 任务划分与分配: 将大任务(文件)分解为小任务(分卷),并均匀分配给多个线程。

  • 独立资源与共享资源: 每个线程拥有独立的文件指针和缓冲区,但共享一个哈希记录文件和其对应的互斥锁。

  • 错误处理与资源清理: 对文件操作、内存分配和线程创建等关键步骤进行错误检查,并确保在程序结束时释放所有资源。

通过这样的多线程设计,可以显著缩短处理大型文件的总时间,是高性能文件操作的典型应用场景。

相关推荐
Tisfy11 分钟前
LeetCode 2411.按位或最大的最小子数组长度:一次倒序遍历
数据结构·算法·leetcode·题解·位运算·遍历
2202_7567496938 分钟前
04 基于sklearn的机械学习-梯度下降(上)
人工智能·算法·机器学习
草莓爱芒果39 分钟前
Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)
java·spring boot·算法
晚云与城1 小时前
【数据结构】-----排序的艺术画卷
数据结构·算法·排序算法
weixin_307779131 小时前
设计Mock CUDA库的流程与实现
c++·算法·gpu算力
j_xxx404_1 小时前
数据结构:算法复杂度与空间复杂度
c语言·数据结构·算法
dlraba8022 小时前
基于 OpenCV 与 sklearn 的数字识别:KNN 算法实践
opencv·算法·sklearn
yzzzzzzzzzzzzzzzzz2 小时前
leetcode热题——全排列
算法·回溯·全排列
王柏龙2 小时前
mongodb中的哈希索引详解
算法·mongodb·哈希算法
NAGNIP2 小时前
GPT1:通用语言理解模型的开端
后端·算法