在日常开发中,我们经常会遇到需要处理大文件的场景,比如将大文件分割成固定大小的分卷以便传输或存储,同时为了保证文件的完整性,还需要对每个分卷进行哈希校验。本文将介绍如何使用 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
: 使用的线程数
实现细节:
-
打开输入文件并验证
-
计算总分卷数(向上取整)
-
创建哈希记录文件(文件名包含线程数)
-
初始化线程和互斥锁
-
均匀分配分卷给各线程(余数分给前几个线程)
-
创建并启动线程
-
等待所有线程完成
-
计算完整文件的SHA256哈希并记录
-
释放所有资源
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) 保护共享资源(哈希记录文件)的访问。
-
任务划分与分配: 将大任务(文件)分解为小任务(分卷),并均匀分配给多个线程。
-
独立资源与共享资源: 每个线程拥有独立的文件指针和缓冲区,但共享一个哈希记录文件和其对应的互斥锁。
-
错误处理与资源清理: 对文件操作、内存分配和线程创建等关键步骤进行错误检查,并确保在程序结束时释放所有资源。
通过这样的多线程设计,可以显著缩短处理大型文件的总时间,是高性能文件操作的典型应用场景。