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

linux中简易云盘系统项目实战:基于 TCP协议的 Socket 通信、json数据交换、MD5文件区别与多用户文件管理实现

📋 项目介绍

本项目是一个基于Linux环境的简易云盘系统,采用C/S(客户端/服务器)架构,实现了类似百度网盘的基本功能。系统通过TCP Socket进行网络通信,使用JSON格式进行数据交换,利用SQLite3数据库存储用户信息和文件元数据,并通过MD5算法实现文件完整性校验和秒传功能。

该系统支持多用户并发访问,具有完善的权限管理机制,区分普通用户、会员用户和管理员三种角色,为不同用户提供差异化的服务。

🚀 实现的功能

1. 用户管理功能

  • 用户注册与登录:新用户可以注册账号,已注册用户可以登录系统
  • 用户角色管理:支持普通用户、会员用户、管理员三种角色
  • 账户注销:用户可以主动删除自己的账户及所有相关数据

2. 文件管理功能

  • 文件上传:支持任意类型文件的上传,自动进行MD5校验
  • 文件下载:可以下载服务器上的文件到本地
  • 文件搜索:支持按文件名关键字搜索服务器文件
  • 秒传功能:相同文件(MD5值相同)无需重复上传,实现秒传

3. 历史记录管理

  • 查看上传历史:用户可以查看自己的文件上传记录
  • 查看下载历史:用户可以查看自己的文件下载记录
  • 删除历史记录:支持删除单条或全部历史记录,下载记录删除时可选择同时删除本地文件

4. 权限控制

  • 文件大小限制:普通用户上传/下载文件大小限制为20KB,会员无限制
  • 管理员特权
    • 设置/取消用户会员资格
    • 删除其他用户账户(管理员账户除外)

5. 系统特性

  • 多客户端并发:支持最多30个客户端同时在线
  • 实时状态显示:服务器端实时显示在线用户列表
  • 数据完整性保证:使用MD5校验确保文件传输的完整性

✨ 项目亮点

1. 秒传技术实现

通过MD5算法计算文件指纹,相同内容的文件只需存储一份,大大节省服务器存储空间,同时实现瞬间上传的用户体验。

2. 完善的权限管理系统

三级用户权限体系(普通用户、会员、管理员),不同权限享有不同的功能和限制,体现了实际云盘系统的设计理念。

3. 健壮的网络通信机制

  • 使用长度前缀的方式传输JSON数据,避免粘包问题
  • 实现了完整的错误处理和断线重连机制
  • 采用多线程处理客户端请求,提高并发性能

4. 用户友好的交互设计

  • 清晰的菜单系统,根据用户身份动态显示可用功能
  • 详细的操作提示和错误信息反馈
  • 支持本地文件管理(下载文件的删除选择)

5. 模块化的代码结构

代码结构清晰,功能模块化,易于维护和扩展。

🛠️ 核心技术

1. TCP Socket编程

  • 使用标准的BSD Socket API实现网络通信
  • 服务器端使用bind()listen()accept()建立监听
  • 客户端使用connect()连接服务器
  • 通过read()/write()进行数据传输

2. 多线程并发处理

  • 使用POSIX线程库(pthread)实现多客户端并发处理
  • 每个客户端连接创建独立线程处理请求
  • 使用互斥锁(mutex)保护共享资源(数据库、客户端列表)

3. JSON数据交换

  • 使用json-c库进行JSON数据的解析和生成
  • 所有客户端-服务器通信采用JSON格式
  • 便于数据结构的扩展和跨平台兼容

4. SQLite3数据库

  • 轻量级嵌入式数据库,无需独立的数据库服务器
  • 存储用户信息、文件元数据、上传下载历史
  • 支持事务处理,保证数据一致性

5. MD5文件校验

  • 使用OpenSSL库的MD5功能
  • 计算文件的MD5值用于完整性校验和去重
  • 实现秒传功能的核心技术

📦 依赖库说明

1. pthread(POSIX线程库)

  • 作用:提供多线程支持,实现并发处理
  • 安装命令
    # Ubuntu/Debian
    sudo apt-get install libpthread-stubs0-dev# CentOS/RHEL
    sudo yum install glibc-devel
    

2. json-c(JSON处理库)

  • 作用:解析和生成JSON格式数据
  • 安装命令
    # Ubuntu/Debian
    sudo apt-get install libjson-c-dev# CentOS/RHEL
    sudo yum install json-c-devel
    

3. OpenSSL(加密库)

  • 作用:提供MD5哈希算法支持
  • 安装命令
    # Ubuntu/Debian
    sudo apt-get install libssl-dev# CentOS/RHEL
    sudo yum install openssl-devel
    

4. SQLite3(数据库库)

  • 作用:提供轻量级数据库功能
  • 安装命令
    # Ubuntu/Debian
    sudo apt-get install libsqlite3-dev sqlite3# CentOS/RHEL
    sudo yum install sqlite-devel sqlite
    

📦 完整代码

client.c

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <json-c/json.h>
#include <openssl/md5.h>
#include <sys/stat.h>
#include <libgen.h>#define BUFFER_SIZE 4096
#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define STORAGE_DIR "client_storage"// 全局客户端状态
int sock;
int is_logged_in = 0, is_admin = 0, is_member = 0, user_id = -1;
char username[50];void send_request(json_object *req);
json_object* receive_response();
char* calculate_file_md5(const char *filepath);
int receive_file(const char *filepath, long long filesize);
int send_file(const char *filepath);
void pre_login_menu();
void post_login_menu();
void handle_register();
void handle_login();
void handle_logout();
void handle_upload();
void handle_download();
void handle_search();
void handle_view_history(const char* type);
void handle_delete_history();
void handle_delete_account();
void handle_admin_set_member();
void handle_admin_delete_user();void safe_exit(int status) {if(sock) close(sock);exit(status);
}int main() {struct sockaddr_in server_addr;mkdir(STORAGE_DIR, 0777);sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1) {perror("无法创建套接字");return 1;}server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);server_addr.sin_port = htons(PORT);if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("连接服务器失败");close(sock);return 1;}printf("已连接到服务器。\n");while (1) {if (!is_logged_in) {pre_login_menu();} else {post_login_menu();}}close(sock);return 0;
}void send_request(json_object *req) {const char *req_str = json_object_to_json_string(req);uint32_t len = htonl(strlen(req_str));if(write(sock, &len, sizeof(len)) < 0 || write(sock, req_str, strlen(req_str)) < 0){printf("发送请求失败,与服务器断开连接。\n");safe_exit(1);}
}int read_all(void *buf, size_t len) {size_t bytes_read = 0;while (bytes_read < len) {ssize_t res = read(sock, (char*)buf + bytes_read, len - bytes_read);if (res <= 0) return -1;bytes_read += res;}return 0;
}json_object* receive_response() {char buffer[BUFFER_SIZE];uint32_t len;if (read_all(&len, sizeof(len)) != 0) return NULL;len = ntohl(len);if (len >= BUFFER_SIZE) {fprintf(stderr, "服务器响应过大。\n");return NULL;}if (read_all(buffer, len) != 0) return NULL;buffer[len] = '\0';return json_tokener_parse(buffer);
}char* calculate_file_md5(const char *filepath) {unsigned char c[MD5_DIGEST_LENGTH];FILE *inFile = fopen(filepath, "rb");MD5_CTX mdContext;int bytes;unsigned char data[1024];static char md5_str[MD5_DIGEST_LENGTH * 2 + 1];if (inFile == NULL) {printf("错误: 文件 %s 无法打开。\n", filepath);return NULL;}MD5_Init(&mdContext);while ((bytes = fread(data, 1, 1024, inFile)) != 0) {MD5_Update(&mdContext, data, bytes);}MD5_Final(c, &mdContext);for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {sprintf(&md5_str[i * 2], "%02x", (unsigned int)c[i]);}md5_str[MD5_DIGEST_LENGTH * 2] = '\0';fclose(inFile);return md5_str;
}int receive_file(const char *filepath, long long filesize) {FILE *fp = fopen(filepath, "wb");if (!fp) {perror("打开文件写入失败");return -1;}char buffer[BUFFER_SIZE];long long received_size = 0;while (received_size < filesize) {int bytes_to_read = (filesize - received_size < BUFFER_SIZE) ? (filesize - received_size) : BUFFER_SIZE;int bytes_read = read(sock, buffer, bytes_to_read);if (bytes_read <= 0) {fclose(fp);return -1;}fwrite(buffer, 1, bytes_read, fp);received_size += bytes_read;}fclose(fp);return 0;
}int send_file(const char *filepath) {FILE *fp = fopen(filepath, "rb");if (!fp) return -1;char buffer[BUFFER_SIZE];size_t bytes_read;while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {if (write(sock, buffer, bytes_read) < 0) {fclose(fp);return -1;}}fclose(fp);return 0;
}void clear_stdin() {int c;while ((c = getchar()) != '\n' && c != EOF);
}void pre_login_menu() {printf("\n===== 欢迎使用模拟网盘 =====\n");printf("1. 登录\n");printf("2. 注册\n");printf("0. 退出\n");printf("请输入您的选择: ");int choice;if (scanf("%d", &choice) != 1) {clear_stdin();printf("无效输入,请输入数字。\n");return;}clear_stdin();switch (choice) {case 1: handle_login(); break;case 2: handle_register(); break;case 0: safe_exit(0);default: printf("无效选择。\n");}
}void post_login_menu() {const char* role = is_admin ? "管理员" : (is_member ? "会员" : "普通用户");printf("\n===== 网盘菜单 (用户: %s, 身份: %s) =====\n", username, role);printf("1. 上传文件\n");printf("2. 下载文件\n");printf("3. 搜索服务器文件\n");printf("4. 查看我的上传\n");printf("5. 查看我的下载\n");printf("6. 管理我的上传和下载\n");printf("7. 注销我的账户\n");if (is_admin) {printf("------ 管理员面板 ------\n");printf("8. 设置/取消用户会员资格\n");printf("9. 删除用户账户\n");}printf("0. 登出\n");printf("请输入您的选择: ");int choice;if (scanf("%d", &choice) != 1) {clear_stdin();printf("无效输入,请输入数字。\n");return;}clear_stdin();switch (choice) {case 1: handle_upload(); break;case 2: handle_download(); break;case 3: handle_search(); break;case 4: handle_view_history("upload"); break;case 5: handle_view_history("download"); break;case 6: handle_delete_history(); break;case 7: handle_delete_account(); break;case 0: handle_logout(); break;default:if (is_admin && (choice == 8 || choice == 9)) {if (choice == 8) handle_admin_set_member();else handle_admin_delete_user();} else {printf("无效选择。\n");}}
}void handle_register() {char reg_user[50], reg_pass[50];printf("请输入注册用户名: ");scanf("%49s", reg_user);printf("请输入密码: ");scanf("%49s", reg_pass);clear_stdin();json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("REGISTER"));json_object_object_add(req, "username", json_object_new_string(reg_user));json_object_object_add(req, "password", json_object_new_string(reg_pass));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); safe_exit(1); }printf("服务器响应: %s\n", json_object_get_string(json_object_object_get(resp, "message")));json_object_put(resp);json_object_put(req);
}void handle_login() {char login_user[50], login_pass[50];printf("请输入用户名: ");scanf("%49s", login_user);printf("请输入密码: ");scanf("%49s", login_pass);clear_stdin();json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("LOGIN"));json_object_object_add(req, "username", json_object_new_string(login_user));json_object_object_add(req, "password", json_object_new_string(login_pass));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); safe_exit(1); }const char *status = json_object_get_string(json_object_object_get(resp, "status"));if (strcmp(status, "SUCCESS") == 0) {is_logged_in = 1;user_id = json_object_get_int(json_object_object_get(resp, "user_id"));is_admin = json_object_get_int(json_object_object_get(resp, "is_admin"));is_member = json_object_get_int(json_object_object_get(resp, "is_member"));strcpy(username, json_object_get_string(json_object_object_get(resp, "username")));printf("登录成功。欢迎您, %s!\n", username);} else {printf("登录失败: %s\n", json_object_get_string(json_object_object_get(resp, "message")));}json_object_put(resp);json_object_put(req);
}void handle_logout() {is_logged_in = 0; is_admin = 0; is_member = 0; user_id = -1;memset(username, 0, sizeof(username));printf("您已成功登出。\n");
}void handle_upload() {char filepath[256];printf("请输入要上传文件的绝对路径或相对路径: ");scanf("%255s", filepath);clear_stdin();struct stat file_stat;if (stat(filepath, &file_stat) < 0) {perror("无法获取文件状态,请检查文件路径是否正确");return;}char *md5 = calculate_file_md5(filepath);if (!md5) return;char *fname = basename(filepath);json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("UPLOAD"));json_object_object_add(req, "filename", json_object_new_string(fname));json_object_object_add(req, "filesize", json_object_new_int64(file_stat.st_size));json_object_object_add(req, "md5", json_object_new_string(md5));send_request(req);json_object_put(req);json_object *resp1 = receive_response();if (!resp1) { printf("与服务器断开连接。\n"); safe_exit(1); }const char *status = json_object_get_string(json_object_object_get(resp1, "status"));const char *message = json_object_get_string(json_object_object_get(resp1, "message"));printf("服务器响应: %s\n", message);if (strcmp(status, "PROCEED_UPLOAD") == 0) {printf("服务器准备就绪,开始文件传输...\n");if (send_file(filepath) == 0) {printf("文件数据已发送,等待服务器最终确认...\n");json_object *resp2 = receive_response();if (resp2) {printf("最终服务器响应: %s\n", json_object_get_string(json_object_object_get(resp2, "message")));json_object_put(resp2);} else {printf("在最终确认阶段与服务器断开连接。\n");json_object_put(resp1);safe_exit(1);}} else {printf("文件传输失败。\n");}}json_object_put(resp1);
}void handle_download() {char filename[256];printf("请输入要下载的文件名: ");scanf("%255s", filename);clear_stdin();json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("DOWNLOAD"));json_object_object_add(req, "filename", json_object_new_string(filename));send_request(req);json_object_put(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); safe_exit(1); }const char *status = json_object_get_string(json_object_object_get(resp, "status"));const char *message = json_object_get_string(json_object_object_get(resp, "message"));printf("服务器响应: %s\n", message);if (strcmp(status, "PROCEED_DOWNLOAD") == 0) {long long filesize = json_object_get_int64(json_object_object_get(resp, "filesize"));char save_path[512];sprintf(save_path, "%s/%s", STORAGE_DIR, filename);printf("正在下载文件到 %s (大小: %lld 字节)...\n", save_path, filesize);if (receive_file(save_path, filesize) == 0) {printf("下载完成!\n");} else {printf("下载失败,连接中断。\n");remove(save_path);}}json_object_put(resp);
}void handle_search() {char keyword[100];printf("请输入要搜索的文件名关键字: ");scanf("%99s", keyword);clear_stdin();json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("SEARCH"));json_object_object_add(req, "keyword", json_object_new_string(keyword));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); json_object_put(req); safe_exit(1); }json_object *data = json_object_object_get(resp, "data");int count = json_object_array_length(data);printf("\n--- 搜索结果 (共 %d 条) ---\n", count);printf("%-30s | %-15s | %s\n", "文件名", "大小 (字节)", "下载次数");printf("----------------------------------------------------------\n");for (int i = 0; i < count; i++) {json_object *row = json_object_array_get_idx(data, i);printf("%-30s | %-15s | %s\n",json_object_get_string(json_object_object_get(row, "filename")),json_object_get_string(json_object_object_get(row, "filesize")),json_object_get_string(json_object_object_get(row, "download_count")));}printf("----------------------------------------------------------\n");json_object_put(resp);json_object_put(req);
}void handle_view_history(const char* type) {json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("VIEW_HISTORY"));json_object_object_add(req, "type", json_object_new_string(type));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); json_object_put(req); safe_exit(1); }const char* type_cn = strcmp(type, "upload") == 0 ? "上传" : "下载";json_object *data = json_object_object_get(resp, "data");int count = json_object_array_length(data);printf("\n--- 我的%s记录 (共 %d 条) ---\n", type_cn, count);printf("%-5s | %-30s | %-15s | %s\n", "ID", "文件名", "大小 (字节)", "时间");printf("----------------------------------------------------------------------\n");for (int i = 0; i < count; i++) {json_object *row = json_object_array_get_idx(data, i);printf("%-5s | %-30s | %-15s | %s\n",json_object_get_string(json_object_object_get(row, "id")),json_object_get_string(json_object_object_get(row, "filename")),json_object_get_string(json_object_object_get(row, "filesize")),json_object_get_string(json_object_object_get(row, (strcmp(type, "upload") == 0 ? "upload_time" : "download_time"))));}printf("----------------------------------------------------------------------\n");json_object_put(resp);json_object_put(req);
}// **重大修改**: handle_delete_history
void handle_delete_history() {char type_str[10];int record_id;printf("要管理哪种历史记录 (upload/download)? ");scanf("%9s", type_str);clear_stdin();if (strcmp(type_str, "upload") != 0 && strcmp(type_str, "download") != 0) {printf("类型无效,必须是 'upload' 或 'download'。\n");return;}printf("请输入要删除的记录ID,或输入-1删除全部: ");if (scanf("%d", &record_id) != 1) {clear_stdin();printf("无效输入,请输入数字。\n");return;}clear_stdin();json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("DELETE_HISTORY"));json_object_object_add(req, "type", json_object_new_string(type_str));json_object_object_add(req, "record_id", json_object_new_int(record_id));send_request(req);json_object_put(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); safe_exit(1); }printf("服务器响应: %s\n", json_object_get_string(json_object_object_get(resp, "message")));// **新增逻辑**: 检查是否需要删除本地文件json_object *deleted_filename_obj;if (json_object_object_get_ex(resp, "deleted_filename", &deleted_filename_obj)) {const char *deleted_filename = json_object_get_string(deleted_filename_obj);printf("是否同时删除本地文件 '%s'? (y/n): ", deleted_filename);char choice;scanf(" %c", &choice);clear_stdin();if (choice == 'y' || choice == 'Y') {char local_filepath[512];sprintf(local_filepath, "%s/%s", STORAGE_DIR, deleted_filename);if (remove(local_filepath) == 0) {printf("本地文件 '%s' 删除成功。\n", deleted_filename);} else {perror("删除本地文件失败");}} else {printf("已保留本地文件。\n");}}json_object_put(resp);
}void handle_delete_account() {char confirmation[10];printf("您确定要永久删除您的账户吗?此操作无法撤销。\n");printf("请输入 'YES' 确认: ");scanf("%9s", confirmation);clear_stdin();if (strcmp(confirmation, "YES") != 0) {printf("账户删除已取消。\n");return;}json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("DELETE_ACCOUNT"));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); json_object_put(req); safe_exit(1); }printf("服务器响应: %s\n", json_object_get_string(json_object_object_get(resp, "message")));if (strcmp(json_object_get_string(json_object_object_get(resp, "status")), "SUCCESS") == 0) {handle_logout();printf("您将被返回到主菜单。\n");}json_object_put(resp);json_object_put(req);
}void handle_admin_set_member() {char target_user[50];int status;printf("请输入要修改的用户名: ");scanf("%49s", target_user);printf("是否设为会员? (1 为是, 0 为否): ");if (scanf("%d", &status) != 1) {clear_stdin();printf("无效输入,请输入数字。\n");return;}clear_stdin();json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("ADMIN_SET_MEMBER"));json_object_object_add(req, "target_user", json_object_new_string(target_user));json_object_object_add(req, "is_member", json_object_new_int(status));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); json_object_put(req); safe_exit(1); }printf("服务器响应: %s\n", json_object_get_string(json_object_object_get(resp, "message")));json_object_put(resp);json_object_put(req);
}void handle_admin_delete_user() {char target_user[50];printf("请输入要删除的用户名: ");scanf("%49s", target_user);clear_stdin();char confirmation[10];printf("警告: 这将永久删除用户 '%s' 及其所有数据。请输入 'DELETE' 确认: ", target_user);scanf("%9s", confirmation);clear_stdin();if (strcmp(confirmation, "DELETE") != 0) {printf("删除用户操作已取消。\n");return;}json_object *req = json_object_new_object();json_object_object_add(req, "command", json_object_new_string("ADMIN_DELETE_USER"));json_object_object_add(req, "target_user", json_object_new_string(target_user));send_request(req);json_object *resp = receive_response();if (!resp) { printf("与服务器断开连接。\n"); json_object_put(req); safe_exit(1); }printf("服务器响应: %s\n", json_object_get_string(json_object_object_get(resp, "message")));json_object_put(resp);json_object_put(req);
}

init_db.c

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>int main() {sqlite3 *db;char *err_msg = 0;// 1. 打开(如果不存在则创建)数据库文件int rc = sqlite3_open("db", &db);if (rc != SQLITE_OK) {fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));sqlite3_close(db);return 1;}printf("数据库打开成功。\n");// 2. 创建用户表 (users)const char *sql_create_users = "CREATE TABLE IF NOT EXISTS users (""id INTEGER PRIMARY KEY AUTOINCREMENT, ""username TEXT NOT NULL UNIQUE, ""password TEXT NOT NULL, ""is_admin INTEGER NOT NULL DEFAULT 0, ""is_member INTEGER NOT NULL DEFAULT 0, ""register_time DATETIME DEFAULT CURRENT_TIMESTAMP);";rc = sqlite3_exec(db, sql_create_users, 0, 0, &err_msg);if (rc != SQLITE_OK) {fprintf(stderr, "SQL错误 (users): %s\n", err_msg);sqlite3_free(err_msg);sqlite3_close(db);return 1;}printf("用户表 'users' 创建成功。\n");// 3. 创建服务端文件信息表 (server_files)const char *sql_create_server_files ="CREATE TABLE IF NOT EXISTS server_files (""id INTEGER PRIMARY KEY AUTOINCREMENT, ""filename TEXT NOT NULL, ""filesize INTEGER NOT NULL, ""md5 TEXT NOT NULL UNIQUE, ""upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, ""download_count INTEGER DEFAULT 0);";rc = sqlite3_exec(db, sql_create_server_files, 0, 0, &err_msg);if (rc != SQLITE_OK) {fprintf(stderr, "SQL错误 (server_files): %s\n", err_msg);sqlite3_free(err_msg);sqlite3_close(db);return 1;}printf("文件信息表 'server_files' 创建成功。\n");// 4. 添加默认管理员账户 (admin/admin)sqlite3_stmt *stmt;const char *sql_check_admin = "SELECT id FROM users WHERE username = 'admin';";rc = sqlite3_prepare_v2(db, sql_check_admin, -1, &stmt, 0);if (rc != SQLITE_OK) {fprintf(stderr, "预处理语句失败: %s\n", sqlite3_errmsg(db));sqlite3_close(db);return 1;}if (sqlite3_step(stmt) != SQLITE_ROW) { // 管理员不存在const char *sql_insert_admin = "INSERT INTO users (username, password, is_admin, is_member) ""VALUES ('admin', 'admin', 1, 1);";rc = sqlite3_exec(db, sql_insert_admin, 0, 0, &err_msg);if (rc != SQLITE_OK) {fprintf(stderr, "SQL错误 (插入管理员): %s\n", err_msg);sqlite3_free(err_msg);} else {printf("默认管理员账户 'admin'/'admin' 创建成功。\n");}} else {printf("管理员账户已存在。\n");}sqlite3_finalize(stmt);// 5. 关闭数据库sqlite3_close(db);printf("数据库初始化完成。\n");return 0;
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#include <sqlite3.h>
#include <json-c/json.h>
#include <openssl/md5.h>
#include <sys/stat.h>
#include <dirent.h>#define BUFFER_SIZE 4096
#define PORT 8888
#define MAX_CLIENTS 30
#define STORAGE_DIR "server_storage"
#define MEMBER_LIMIT 20480 // 20KB// 全局变量
sqlite3 *db;
pthread_mutex_t db_mutex;
pthread_mutex_t clients_mutex;// 客户端信息结构
typedef struct {int sock;struct sockaddr_in address;int user_id;char username[50];int is_logged_in;int is_admin;int is_member;
} client_t;client_t *clients[MAX_CLIENTS];// 函数声明
void add_client(client_t *cl);
void remove_client(int sock);
void print_connected_clients();
char* calculate_file_md5(const char *filepath);
void handle_client(void *arg);
json_object* process_request(json_object *req, client_t *client_info);// 主函数
int main() {mkdir(STORAGE_DIR, 0777);if (sqlite3_open("db", &db)) {fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));return 1;} else {fprintf(stdout, "数据库打开成功\n");}pthread_mutex_init(&db_mutex, NULL);pthread_mutex_init(&clients_mutex, NULL);int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);pthread_t tid;server_sock = socket(AF_INET, SOCK_STREAM, 0);if (server_sock == -1) {perror("无法创建套接字");return 1;}int opt = 1;if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {perror("setsockopt(SO_REUSEADDR) 失败");exit(EXIT_FAILURE);}server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(PORT);if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("绑定失败");close(server_sock);return 1;}printf("服务器绑定成功\n");listen(server_sock, 5);printf("服务器正在监听端口 %d...\n", PORT);printf("等待客户端连接...\n");while (1) {client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);if (client_sock < 0) {perror("接受连接失败");continue;}printf("接受来自 %s:%d 的连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));client_t *cli = (client_t *)malloc(sizeof(client_t));cli->address = client_addr;cli->sock = client_sock;cli->user_id = -1;cli->is_logged_in = 0;cli->is_admin = 0;cli->is_member = 0;memset(cli->username, 0, sizeof(cli->username));add_client(cli);if (pthread_create(&tid, NULL, (void *)handle_client, (void *)cli) < 0) {perror("无法创建线程");free(cli);close(client_sock);}}close(server_sock);sqlite3_close(db);pthread_mutex_destroy(&db_mutex);pthread_mutex_destroy(&clients_mutex);return 0;
}void add_client(client_t *cl) {pthread_mutex_lock(&clients_mutex);for (int i = 0; i < MAX_CLIENTS; i++) {if (!clients[i]) {clients[i] = cl;break;}}pthread_mutex_unlock(&clients_mutex);
}void remove_client(int sock) {pthread_mutex_lock(&clients_mutex);for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i] && clients[i]->sock == sock) {printf("客户端断开连接: %s (套接字: %d)\n", clients[i]->is_logged_in ? clients[i]->username : "未登录", sock);free(clients[i]);clients[i] = NULL;break;}}pthread_mutex_unlock(&clients_mutex);print_connected_clients();
}void print_connected_clients() {pthread_mutex_lock(&clients_mutex);printf("\n--- 当前在线客户端 ---\n");int count = 0;for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i] && clients[i]->is_logged_in) {const char* role = clients[i]->is_admin ? "管理员" : (clients[i]->is_member ? "会员" : "普通用户");printf("  - %s (用户ID: %d, 身份: %s)\n", clients[i]->username, clients[i]->user_id, role);count++;}}if (count == 0) {printf("  无已登录的客户端。\n");}printf("----------------------\n");pthread_mutex_unlock(&clients_mutex);
}char* calculate_file_md5(const char *filepath) {unsigned char c[MD5_DIGEST_LENGTH];FILE *inFile = fopen(filepath, "rb");MD5_CTX mdContext;int bytes;unsigned char data[1024];char *md5_str = (char*)malloc(MD5_DIGEST_LENGTH * 2 + 1);if (md5_str == NULL) {fprintf(stderr, "为MD5字符串分配内存失败\n");if (inFile) fclose(inFile);return NULL;}if (inFile == NULL) {fprintf(stderr, "文件 %s 无法打开。\n", filepath);free(md5_str);return NULL;}MD5_Init(&mdContext);while ((bytes = fread(data, 1, 1024, inFile)) != 0) {MD5_Update(&mdContext, data, bytes);}MD5_Final(c, &mdContext);for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {sprintf(&md5_str[i * 2], "%02x", (unsigned int)c[i]);}md5_str[MD5_DIGEST_LENGTH * 2] = '\0';fclose(inFile);return md5_str;
}void send_response(int sock, json_object *response) {const char *response_str = json_object_to_json_string(response);uint32_t len = htonl(strlen(response_str));write(sock, &len, sizeof(len));write(sock, response_str, strlen(response_str));
}int read_all(int sock, void *buf, size_t len) {size_t bytes_read = 0;while (bytes_read < len) {ssize_t res = read(sock, (char*)buf + bytes_read, len - bytes_read);if (res <= 0) return -1;bytes_read += res;}return 0;
}int receive_file(int sock, const char *filepath, long long filesize) {FILE *fp = fopen(filepath, "wb");if (!fp) {perror("打开文件写入失败");return -1;}char buffer[BUFFER_SIZE];long long received_size = 0;while (received_size < filesize) {int bytes_to_read = (filesize - received_size < BUFFER_SIZE) ? (filesize - received_size) : BUFFER_SIZE;int bytes_read = read(sock, buffer, bytes_to_read);if (bytes_read <= 0) {fclose(fp);return -1;}fwrite(buffer, 1, bytes_read, fp);received_size += bytes_read;}fclose(fp);return 0;
}int send_file(int sock, const char *filepath) {FILE *fp = fopen(filepath, "rb");if (!fp) return -1;char buffer[BUFFER_SIZE];size_t bytes_read;while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {if (write(sock, buffer, bytes_read) < 0) {fclose(fp);return -1;}}fclose(fp);return 0;
}static int single_value_callback(void *data, int argc, char **argv, char **azColName) {if (argc > 0 && argv[0]) strcpy((char *)data, argv[0]);return 0;
}void record_user_upload(int user_id, const char* filename, long long filesize) {char user_upload_table[100];sprintf(user_upload_table, "user_uploads_%d", user_id);char create_table_sql[256];sprintf(create_table_sql, "CREATE TABLE IF NOT EXISTS %s (""id INTEGER PRIMARY KEY AUTOINCREMENT, ""filename TEXT, filesize INTEGER, ""upload_time DATETIME DEFAULT CURRENT_TIMESTAMP);", user_upload_table);char insert_sql[1024];sprintf(insert_sql, "INSERT INTO %s (filename, filesize) VALUES ('%s', %lld);", user_upload_table, filename, filesize);sqlite3_exec(db, create_table_sql, 0, 0, NULL);sqlite3_exec(db, insert_sql, 0, 0, NULL);
}void do_register(json_object *req, json_object *resp) {const char *username = json_object_get_string(json_object_object_get(req, "username"));const char *password = json_object_get_string(json_object_object_get(req, "password"));char sql[256], *err_msg = 0;sprintf(sql, "INSERT INTO users (username, password) VALUES ('%s', '%s');", username, password);pthread_mutex_lock(&db_mutex);int rc = sqlite3_exec(db, sql, 0, 0, &err_msg);pthread_mutex_unlock(&db_mutex);if (rc != SQLITE_OK) {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("用户名已存在或数据库错误。"));sqlite3_free(err_msg);} else {json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(resp, "message", json_object_new_string("注册成功。"));}
}void do_login(json_object *req, json_object *resp, client_t *client_info) {const char *username = json_object_get_string(json_object_object_get(req, "username"));const char *password = json_object_get_string(json_object_object_get(req, "password"));char sql[512];sqlite3_stmt *stmt;sprintf(sql, "SELECT id, is_admin, is_member FROM users WHERE username = ? AND password = ?;");pthread_mutex_lock(&db_mutex);int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);if (rc == SQLITE_OK) {sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC);if (sqlite3_step(stmt) == SQLITE_ROW) {client_info->user_id = sqlite3_column_int(stmt, 0);strcpy(client_info->username, username);client_info->is_logged_in = 1;client_info->is_admin = sqlite3_column_int(stmt, 1);client_info->is_member = sqlite3_column_int(stmt, 2);json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(resp, "message", json_object_new_string("登录成功。"));json_object_object_add(resp, "user_id", json_object_new_int(client_info->user_id));json_object_object_add(resp, "username", json_object_new_string(client_info->username));json_object_object_add(resp, "is_admin", json_object_new_int(client_info->is_admin));json_object_object_add(resp, "is_member", json_object_new_int(client_info->is_member));print_connected_clients();} else {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("用户名不存在或密码错误。"));}} else {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("数据库查询失败。"));}sqlite3_finalize(stmt);pthread_mutex_unlock(&db_mutex);
}void do_upload(json_object *req, json_object *resp, client_t *client_info) {const char *filename = json_object_get_string(json_object_object_get(req, "filename"));long long filesize = json_object_get_int64(json_object_object_get(req, "filesize"));const char *md5_from_client = json_object_get_string(json_object_object_get(req, "md5"));if (!client_info->is_member && filesize > MEMBER_LIMIT) {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("权限不足:普通用户上传文件大小不能超过20KB。"));return;}char sql[512];sqlite3_stmt *stmt;sprintf(sql, "SELECT filename FROM server_files WHERE md5 = ?;");pthread_mutex_lock(&db_mutex);int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);sqlite3_bind_text(stmt, 1, md5_from_client, -1, SQLITE_STATIC);if (rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) {sqlite3_finalize(stmt);record_user_upload(client_info->user_id, filename, filesize);pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("SUCCESS_SECONDUPLOAD"));json_object_object_add(resp, "message", json_object_new_string("文件已存在于服务器,秒传成功。"));return;}sqlite3_finalize(stmt);pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("PROCEED_UPLOAD"));json_object_object_add(resp, "message", json_object_new_string("服务器准备就绪,请开始上传文件。"));
}void do_download(json_object *req, json_object *resp, client_t *client_info) {const char* filename = json_object_get_string(json_object_object_get(req, "filename"));char filepath[512];sprintf(filepath, "%s/%s", STORAGE_DIR, filename);struct stat file_stat;if (stat(filepath, &file_stat) < 0) {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("文件在服务器上不存在。"));return;}long long filesize = file_stat.st_size;if (!client_info->is_member && filesize > MEMBER_LIMIT) {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("权限不足:普通用户下载文件大小不能超过20KB。"));return;}json_object_object_add(resp, "status", json_object_new_string("PROCEED_DOWNLOAD"));json_object_object_add(resp, "message", json_object_new_string("文件找到,开始下载。"));json_object_object_add(resp, "filesize", json_object_new_int64(filesize));
}struct query_result { json_object *jarray; };
static int query_to_json_callback(void *data, int argc, char **argv, char **azColName) {struct query_result *res = (struct query_result *)data;json_object *jrow = json_object_new_object();for (int i = 0; i < argc; i++) {json_object_object_add(jrow, azColName[i], json_object_new_string(argv[i] ? argv[i] : "NULL"));}json_object_array_add(res->jarray, jrow);return 0;
}void do_search(json_object *req, json_object *resp) {const char* keyword = json_object_get_string(json_object_object_get(req, "keyword"));char sql[512];sprintf(sql, "SELECT filename, filesize, download_count FROM server_files WHERE filename LIKE '%%%s%%';", keyword);struct query_result res;res.jarray = json_object_new_array();pthread_mutex_lock(&db_mutex);sqlite3_exec(db, sql, query_to_json_callback, &res, NULL);pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(resp, "data", res.jarray);
}void do_view_history(json_object *req, json_object *resp, client_t *client_info) {const char *type = json_object_get_string(json_object_object_get(req, "type"));char table_name[100];sprintf(table_name, "user_%ss_%d", type, client_info->user_id);char sql[512];sprintf(sql, "SELECT id, filename, filesize, %s_time FROM %s;", type, table_name);struct query_result res = { .jarray = json_object_new_array() };pthread_mutex_lock(&db_mutex);char check_sql[256];sqlite3_stmt *stmt;sprintf(check_sql, "SELECT name FROM sqlite_master WHERE type='table' AND name='%s';", table_name);if (sqlite3_prepare_v2(db, check_sql, -1, &stmt, 0) == SQLITE_OK) {if (sqlite3_step(stmt) == SQLITE_ROW) {sqlite3_exec(db, sql, query_to_json_callback, &res, NULL);}sqlite3_finalize(stmt);}pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(resp, "data", res.jarray);
}// **重大修改**: do_delete_history
void do_delete_history(json_object *req, json_object *resp, client_t *client_info) {const char *type = json_object_get_string(json_object_object_get(req, "type"));int record_id = json_object_get_int(json_object_object_get(req, "record_id"));char table_name[100];sprintf(table_name, "user_%ss_%d", type, client_info->user_id);char sql[256];pthread_mutex_lock(&db_mutex);if (record_id != -1 && strcmp(type, "download") == 0) {// 如果是删除单条下载记录,先查询出文件名char filename_buf[256] = {0};sprintf(sql, "SELECT filename FROM %s WHERE id = %d;", table_name, record_id);sqlite3_exec(db, sql, single_value_callback, filename_buf, NULL);if (strlen(filename_buf) > 0) {json_object_object_add(resp, "deleted_filename", json_object_new_string(filename_buf));}}if (record_id == -1) { // 删除全部sprintf(sql, "DELETE FROM %s;", table_name);} else { // 删除单条sprintf(sql, "DELETE FROM %s WHERE id = %d;", table_name, record_id);}sqlite3_exec(db, sql, 0, 0, NULL);pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(resp, "message", json_object_new_string("历史记录已删除。"));
}void do_delete_account(json_object *resp, client_t *client_info) {char sql[512], table_name[100];pthread_mutex_lock(&db_mutex);sprintf(sql, "DELETE FROM users WHERE id = %d;", client_info->user_id);sqlite3_exec(db, sql, 0, 0, NULL);sprintf(table_name, "user_uploads_%d", client_info->user_id);sprintf(sql, "DROP TABLE IF EXISTS %s;", table_name);sqlite3_exec(db, sql, 0, 0, NULL);sprintf(table_name, "user_downloads_%d", client_info->user_id);sprintf(sql, "DROP TABLE IF EXISTS %s;", table_name);sqlite3_exec(db, sql, 0, 0, NULL);pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(resp, "message", json_object_new_string("您的账户已成功删除。"));
}void do_admin_set_member(json_object *req, json_object *resp) {const char *target_user = json_object_get_string(json_object_object_get(req, "target_user"));int is_member = json_object_get_int(json_object_object_get(req, "is_member"));char sql[256];sprintf(sql, "UPDATE users SET is_member = %d WHERE username = '%s';", is_member, target_user);pthread_mutex_lock(&db_mutex);sqlite3_exec(db, sql, 0, 0, NULL);int changes = sqlite3_changes(db);pthread_mutex_unlock(&db_mutex);if (changes > 0) {json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));char msg[128];sprintf(msg, "用户 '%s' 的会员状态已更新。", target_user);json_object_object_add(resp, "message", json_object_new_string(msg));} else {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("未找到该用户。"));}
}void do_admin_delete_user(json_object *req, json_object *resp) {const char *target_user = json_object_get_string(json_object_object_get(req, "target_user"));if (strcmp(target_user, "admin") == 0) {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("不能删除管理员账户。"));return;}char sql[512], target_id_str[20] = {0};pthread_mutex_lock(&db_mutex);sprintf(sql, "SELECT id FROM users WHERE username = '%s';", target_user);sqlite3_exec(db, sql, single_value_callback, target_id_str, NULL);if (strlen(target_id_str) == 0) {pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("未找到该用户。"));return;}int target_id = atoi(target_id_str);sprintf(sql, "DELETE FROM users WHERE id = %d;", target_id);sqlite3_exec(db, sql, 0, 0, NULL);char table_name[100];sprintf(table_name, "user_uploads_%d", target_id);sprintf(sql, "DROP TABLE IF EXISTS %s;", table_name);sqlite3_exec(db, sql, 0, 0, NULL);sprintf(table_name, "user_downloads_%d", target_id);sprintf(sql, "DROP TABLE IF EXISTS %s;", table_name);sqlite3_exec(db, sql, 0, 0, NULL);pthread_mutex_unlock(&db_mutex);json_object_object_add(resp, "status", json_object_new_string("SUCCESS"));char msg[128];sprintf(msg, "用户 '%s' 及其所有关联数据已被删除。", target_user);json_object_object_add(resp, "message", json_object_new_string(msg));
}json_object* process_request(json_object *req, client_t *client_info) {json_object *resp = json_object_new_object();const char *command = json_object_get_string(json_object_object_get(req, "command"));if (strcmp(command, "REGISTER") == 0)      { do_register(req, resp); } else if (strcmp(command, "LOGIN") == 0)    { do_login(req, resp, client_info); } else if (client_info->is_logged_in) {if (strcmp(command, "UPLOAD") == 0)             { do_upload(req, resp, client_info); } else if (strcmp(command, "DOWNLOAD") == 0)          { do_download(req, resp, client_info); } else if (strcmp(command, "SEARCH") == 0)            { do_search(req, resp); } else if (strcmp(command, "VIEW_HISTORY") == 0)      { do_view_history(req, resp, client_info); } else if (strcmp(command, "DELETE_HISTORY") == 0)    { do_delete_history(req, resp, client_info); } else if (strcmp(command, "DELETE_ACCOUNT") == 0)    { do_delete_account(resp, client_info); } else if (client_info->is_admin) {if (strcmp(command, "ADMIN_SET_MEMBER") == 0)   { do_admin_set_member(req, resp); } else if (strcmp(command, "ADMIN_DELETE_USER") == 0) { do_admin_delete_user(req, resp); }else {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("未知管理员命令。"));}} else {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("未知命令或权限不足。"));}} else {json_object_object_add(resp, "status", json_object_new_string("ERROR"));json_object_object_add(resp, "message", json_object_new_string("需要认证,请先登录。"));}return resp;
}void handle_client(void *arg) {client_t *cli = (client_t *)arg;char buffer[BUFFER_SIZE];while (1) {uint32_t len;if (read_all(cli->sock, &len, sizeof(len)) != 0) break;len = ntohl(len);if (len >= BUFFER_SIZE) {fprintf(stderr, "来自套接字 %d 的请求过大,断开连接。\n", cli->sock);break;}if (read_all(cli->sock, buffer, len) != 0) break;buffer[len] = '\0';json_object *req = json_tokener_parse(buffer);if (!req) {fprintf(stderr, "来自套接字 %d 的JSON无效,断开连接。\n", cli->sock);break;}json_object *resp = process_request(req, cli);const char *cmd = json_object_get_string(json_object_object_get(req, "command"));const char *status_str = json_object_get_string(json_object_object_get(resp, "status"));send_response(cli->sock, resp);if (status_str && strcmp(status_str, "PROCEED_UPLOAD") == 0) {const char *filename = json_object_get_string(json_object_object_get(req, "filename"));long long filesize = json_object_get_int64(json_object_object_get(req, "filesize"));const char *md5_from_client = json_object_get_string(json_object_object_get(req, "md5"));char temp_filepath[512];sprintf(temp_filepath, "%s/%s.tmp", STORAGE_DIR, filename);if (receive_file(cli->sock, temp_filepath, filesize) != 0) {fprintf(stderr, "接收文件 %s 失败\n", filename);remove(temp_filepath);} else {char *received_md5 = calculate_file_md5(temp_filepath);json_object *final_resp = json_object_new_object();if (received_md5 && strcmp(received_md5, md5_from_client) == 0) {char final_filepath[512];sprintf(final_filepath, "%s/%s", STORAGE_DIR, filename);rename(temp_filepath, final_filepath);pthread_mutex_lock(&db_mutex);char sql[512];sprintf(sql, "INSERT OR IGNORE INTO server_files (filename, filesize, md5) VALUES ('%s', %lld, '%s');", filename, filesize, received_md5);sqlite3_exec(db, sql, 0, 0, NULL);record_user_upload(cli->user_id, filename, filesize);pthread_mutex_unlock(&db_mutex);json_object_object_add(final_resp, "status", json_object_new_string("SUCCESS"));json_object_object_add(final_resp, "message", json_object_new_string("文件上传并校验成功。"));} else {remove(temp_filepath);json_object_object_add(final_resp, "status", json_object_new_string("ERROR"));json_object_object_add(final_resp, "message", json_object_new_string("文件传输错误:MD5校验不匹配,请重新上传。"));}if (received_md5) free(received_md5);send_response(cli->sock, final_resp);json_object_put(final_resp);}} else if (status_str && strcmp(status_str, "PROCEED_DOWNLOAD") == 0) {const char* filename = json_object_get_string(json_object_object_get(req, "filename"));long long filesize = json_object_get_int64(json_object_object_get(resp, "filesize"));char filepath[512];sprintf(filepath, "%s/%s", STORAGE_DIR, filename);if (send_file(cli->sock, filepath) == 0) {pthread_mutex_lock(&db_mutex);char sql[512];sprintf(sql, "UPDATE server_files SET download_count = download_count + 1 WHERE filename = '%s';", filename);sqlite3_exec(db, sql, 0, 0, NULL);char user_download_table[100];sprintf(user_download_table, "user_downloads_%d", cli->user_id);char create_table_sql[256];sprintf(create_table_sql, "CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT, filesize INTEGER, download_time DATETIME DEFAULT CURRENT_TIMESTAMP);", user_download_table);sqlite3_exec(db, create_table_sql, 0, 0, NULL);sprintf(sql, "INSERT INTO %s (filename, filesize) VALUES ('%s', %lld);", user_download_table, filename, filesize);sqlite3_exec(db, sql, 0, 0, NULL);pthread_mutex_unlock(&db_mutex);} else {fprintf(stderr, "发送文件 %s 到客户端 %s 失败\n", filename, cli->username);}}if (status_str && strcmp(cmd, "DELETE_ACCOUNT") == 0 && strcmp(status_str, "SUCCESS") == 0) {json_object_put(req);json_object_put(resp);break; }json_object_put(req);json_object_put(resp);}close(cli->sock);remove_client(cli->sock);pthread_detach(pthread_self());
}

Makefile

# Makefile for the Simulated Network Disk Project# Compiler and flags
CC = gcc
CFLAGS = -Wall -g  # -Wall enables all warnings, -g adds debug info
LDFLAGS_SERVER = -lsqlite3 -lpthread -ljson-c -lcrypto
LDFLAGS_CLIENT = -ljson-c -lcrypto -lpthread
LDFLAGS_INITDB = -lsqlite3# Executable names
SERVER_EXEC = server
CLIENT_EXEC = client
INITDB_EXEC = init_db# Source files
SERVER_SRC = server.c
CLIENT_SRC = client.c
INITDB_SRC = init_db.c# Phony targets
.PHONY: all clean# Default target
all: $(SERVER_EXEC) $(CLIENT_EXEC) $(INITDB_EXEC)# Rule to build the server
$(SERVER_EXEC): $(SERVER_SRC)$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS_SERVER)# Rule to build the client
$(CLIENT_EXEC): $(CLIENT_SRC)$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS_CLIENT)# Rule to build the database initializer
$(INITDB_EXEC): $(INITDB_SRC)$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS_INITDB)# Clean up build artifacts
clean:rm -f $(SERVER_EXEC) $(CLIENT_EXEC) $(INITDB_EXEC) *.o

📖 详细使用方法

1. 环境准备

首先确保系统已安装所有依赖库:

# 一键安装所有依赖(Ubuntu/Debian)
sudo apt-get update
sudo apt-get install build-essential libpthread-stubs0-dev libjson-c-dev libssl-dev libsqlite3-dev sqlite3# 一键安装所有依赖(CentOS/RHEL)
sudo yum groupinstall "Development Tools"
sudo yum install glibc-devel json-c-devel openssl-devel sqlite-devel sqlite

2. 编译项目

项目提供了Makefile文件,可以方便地编译所有组件:

# 编译所有程序
make all# 或者分别编译
make server    # 编译服务器端
make client    # 编译客户端
make init_db   # 编译数据库初始化程序# 清理编译文件
make clean

3. 初始化数据库

首次使用前需要初始化数据库:

./init_db

这将创建:

  • 用户表(users)
  • 服务器文件信息表(server_files)
  • 默认管理员账户(用户名:admin,密码:admin)

4. 启动服务器

./server

服务器将在8888端口监听客户端连接。启动后会显示:

数据库打开成功
服务器绑定成功
服务器正在监听端口 8888...
等待客户端连接...

5. 启动客户端

在另一个终端窗口运行:

./client

6. 使用流程示例

6.1 新用户注册
===== 欢迎使用模拟网盘 =====
1. 登录
2. 注册
0. 退出
请输入您的选择: 2
请输入注册用户名: testuser
请输入密码: 123456
服务器响应: 注册成功。
6.2 用户登录
请输入您的选择: 1
请输入用户名: testuser
请输入密码: 123456
登录成功。欢迎您, testuser!
6.3 上传文件
===== 网盘菜单 (用户: testuser, 身份: 普通用户) =====
1. 上传文件
...
请输入您的选择: 1
请输入要上传文件的绝对路径或相对路径: /home/user/test.txt
服务器响应: 服务器准备就绪,请开始上传文件。
服务器准备就绪,开始文件传输...
文件数据已发送,等待服务器最终确认...
最终服务器响应: 文件上传并校验成功。
6.4 管理员操作

使用admin账户登录后,可以进行管理操作:

------ 管理员面板 ------
8. 设置/取消用户会员资格
9. 删除用户账户

7. 目录结构

运行后会自动创建以下目录:

  • server_storage/ - 服务器端文件存储目录
  • client_storage/ - 客户端下载文件存储目录
  • db - SQLite数据库文件

8. 注意事项

  1. 端口配置:默认使用8888端口,如需修改请同时修改server.c和client.c中的PORT定义
  2. IP配置:客户端默认连接127.0.0.1(本地),如需远程连接请修改client.c中的SERVER_IP
  3. 文件大小限制:普通用户限制为20KB,可在server.c中的MEMBER_LIMIT修改
  4. 并发连接数:最大支持30个客户端,可在server.c中的MAX_CLIENTS修改

🔧 故障排除

1. 编译错误

  • 确保所有依赖库已正确安装
  • 检查gcc版本是否支持C99标准

2. 连接失败

  • 确保服务器已启动
  • 检查防火墙是否开放8888端口
  • 确认IP地址配置正确

3. 数据库错误

  • 确保已运行init_db初始化数据库
  • 检查当前用户是否有读写db文件的权限

📝 总结

本项目实现了一个功能完整的简易云盘系统,涵盖了网络编程、数据库操作、多线程并发、文件处理等多个Linux系统编程的核心技术。通过本项目的学习和实践,可以深入理解:

  • TCP Socket编程的原理和实践
  • 多线程并发服务器的设计与实现
  • JSON数据交换格式的应用
  • SQLite数据库的使用
  • 文件完整性校验和去重技术

文章到此结束啦,如果觉得本文章对您有所帮助,请点个赞和关注以及收藏,你的支持就是我继续更新的最大动力,谢谢!!!!

http://www.lryc.cn/news/603389.html

相关文章:

  • uniapp 微信小程序 列表点击分享 不同的信息
  • YOLO--目标检测基础
  • 计算机视觉-图像基础处理
  • TailWindCss安装使用教程
  • eudev是什么东西,有什么作用
  • 1768. 交替合并字符串
  • 无线网络优化实践
  • [学习记录]URP流程解析(2)--初始化阶段
  • 虚拟机网络修复
  • 充电宝自燃隐患引发关注:如何确保充电宝安全?
  • 门控激活函数:GLU/GTU/Swish/HSwish/Mish/SwiGLU
  • 机器学习sklearn:泰坦尼克幸存预测(决策树、网格搜索找最佳参数)
  • 【深度学习新浪潮】什么是世界模型?
  • fastApi中的ocr
  • 译 | 介绍PyTabKit:一个试图超越 Scikit-Learn的新机器学习库
  • 如何查询并访问路由器的默认网关(IP地址)?
  • 主应用严格模式下,子应用组件el-date-picker点击无效
  • 【Dify】-进阶14- 用 Dify 搭建法律文档解析助手
  • Vue.js 指令系统完全指南:深入理解 v- 指令
  • 智能图书馆管理系统开发实战系列(一):项目架构设计与技术选型
  • Ubuntu上开通Samba网络共享
  • Ambari 3.0.0 全网首发支持 Ubuntu 22!
  • Kafka——消费者组重平衡全流程解析
  • cpolar 内网穿透 ubuntu 使用石
  • Spark SQL 数组函数合集:array_agg、array_contains、array_sort…详解
  • 【MySQL】从连接数据库开始:JDBC 编程入门指南
  • Vim与VS Code
  • 【CodeTop】每日练习 2025.7.29
  • LibTorch使用-基础版
  • Jetpack - Room(Room 引入、Room 优化)