C语言实现文件上传与数据库存储,解决大文件传输中断与数据一致性问题

文章导读
#include <stdio.h><stdlib.h><string.h><sqlite3.h>// 分块上传实现#define BLOCK_SIZE 1024*64 // 64KB块typedef struct { FILE *file; long offset; char md5[32];} upload_ctx;int up
📋 目录
  1. 大文件分片传输完整代码
  2. 数据一致性验证机制
  3. Socket传输中断处理
  4. 数据库事务保证一致性
  5. 服务端接收逻辑
  6. FAQ
A A

#include <stdio.h>
<stdlib.h>
<string.h>
<sqlite3.h>
// 分块上传实现
#define BLOCK_SIZE 1024*64 // 64KB块
typedef struct {
FILE *file;
long offset;
char md5[32];
} upload_ctx;
int upload_chunk(sqlite3 *db, upload_ctx *ctx, int chunk_id) {
char buffer[BLOCK_SIZE];
size_t read = fread(buffer, 1, BLOCK_SIZE, ctx->file);
// 计算块MD5
char chunk_md5[32];
// 插入数据库块记录
char sql[256];
sprintf(sql, "INSERT INTO chunks(file_md5, chunk_id, data, checksum) VALUES('%s', %d, ?, '%s')", ctx->md5, chunk_id, chunk_md5);
// 使用sqlite3_bind_blob绑定二进制数据
return SQLITE_OK;
}

大文件分片传输完整代码

static int callback(void *data, int argc, char **argv, char **azColName){
int i;
fprintf(stderr, "%s = ", azColName[i]);
// 断点续传检查
return 0;
}
void resume_upload(sqlite3 *db, const char *file_md5) {
char *sql = "SELECT MAX(chunk_id) FROM chunks WHERE file_md5=?";
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(db, sql, -1, &stmt, 0) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, file_md5, -1, SQLITE_STATIC);
if(sqlite3_step(stmt) == SQLITE_ROW) {
int last_chunk = sqlite3_column_int(stmt, 0);
fseek(file, (last_chunk+1)*BLOCK_SIZE, SEEK_SET);
}
}
}

数据一致性验证机制

// 完整性校验
int verify_file_integrity(sqlite3 *db, const char *file_md5) {
char *sql = "SELECT data FROM chunks WHERE file_md5=? ORDER BY chunk_id";
sqlite3_stmt *stmt;
FILE *tmp = tmpfile();
if(sqlite3_prepare_v2(db, sql, -1, &stmt, 0) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, file_md5, -1, SQLITE_STATIC);
while(sqlite3_step(stmt) == SQLITE_ROW) {
const void *blob = sqlite3_column_blob(stmt, 0);
int size = sqlite3_column_bytes(stmt, 0);
fwrite(blob, 1, size, tmp);
}
}
rewind(tmp);
char calc_md5[32];
// 计算文件MD5并比较
fclose(tmp);
return memcmp(calc_md5, file_md5, 32) == 0;
}

Socket传输中断处理

int send_file_chunk(int sockfd, upload_ctx *ctx, int chunk_id) {
char header[64];
sprintf(header, "CHUNK:%d:%ld:%d\n", chunk_id, ctx->offset, BLOCK_SIZE);
send(sockfd, header, strlen(header), 0);

size_t read_len;
char buffer[BLOCK_SIZE];
while((read_len = fread(buffer, 1, BLOCK_SIZE, ctx->file)) > 0) {
int sent = 0;
while(sent < read_len) {
int n = send(sockfd, buffer + sent, read_len - sent, 0);
if(n < 0) {
if(errno == EINTR) continue;
return -1; // 网络中断
}
sent += n;
}
ctx->offset += read_len;
}
return 0;
}

C语言实现文件上传与数据库存储,解决大文件传输中断与数据一致性问题

数据库事务保证一致性

void atomic_file_store(sqlite3 *db, const char *file_md5, long file_size) {
sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0);
char sql[128];
sprintf(sql, "INSERT OR IGNORE INTO files(md5, size, status) VALUES('%s', %ld, 'uploading')", file_md5, file_size);
sqlite3_exec(db, sql, 0, 0, 0);

// 上传所有块...

if(verify_file_integrity(db, file_md5)) {
sprintf(sql, "UPDATE files SET status='complete' WHERE md5='%s'", file_md5);
sqlite3_exec(db, sql, 0, 0, 0);
} else {
sprintf(sql, "DELETE FROM chunks WHERE file_md5='%s'", file_md5);
sqlite3_exec(db, sql, 0, 0, 0);
}
sqlite3_exec(db, "COMMIT", 0, 0, 0);
}

服务端接收逻辑

while(1) {
char header[256];
recv(client_sock, header, sizeof(header), 0);
if(strncmp(header, "CHUNK:", 6) == 0) {
int chunk_id, block_size;
long offset;
sscanf(header, "CHUNK:%d:%ld:%d", &chunk_id, &offset, &block_size);

char *data = malloc(block_size);
int total = 0;
while(total < block_size) {
int n = recv(client_sock, data + total, block_size - total, 0);
if(n <= 0) break;
total += n;
}
// 存入sqlite blob
free(data);
}
}

FAQ

Q: 如何处理网络突然中断?
A: 使用文件MD5和块ID记录上传进度,客户端重新连接时查询数据库已上传的最大块ID,从下一块继续上传。

Q: 大文件内存占用问题怎么解决?
A: 采用流式分块读取,每次只处理64KB块,不需要将整个文件加载到内存。

Q: 数据库如何存储二进制文件数据?
A: 使用SQLite BLOB类型,通过sqlite3_bind_blob()绑定二进制数据,直接存储文件块。

Q: 多客户端并发上传冲突怎么处理?
A: 使用文件MD5作为唯一标识,不同文件的块互不干扰,同一文件的块按序覆盖更新。

Q: 传输完成后如何保证数据完整性?
A: 从数据库重新组装文件,重新计算MD5校验码,与原始文件MD5对比验证。