📋 项目介绍
本项目是一个基于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. 注意事项
- 端口配置:默认使用8888端口,如需修改请同时修改server.c和client.c中的PORT定义
- IP配置:客户端默认连接127.0.0.1(本地),如需远程连接请修改client.c中的SERVER_IP
- 文件大小限制:普通用户限制为20KB,可在server.c中的MEMBER_LIMIT修改
- 并发连接数:最大支持30个客户端,可在server.c中的MAX_CLIENTS修改
🔧 故障排除
1. 编译错误
- 确保所有依赖库已正确安装
- 检查gcc版本是否支持C99标准
2. 连接失败
- 确保服务器已启动
- 检查防火墙是否开放8888端口
- 确认IP地址配置正确
3. 数据库错误
- 确保已运行init_db初始化数据库
- 检查当前用户是否有读写db文件的权限
📝 总结
本项目实现了一个功能完整的简易云盘系统,涵盖了网络编程、数据库操作、多线程并发、文件处理等多个Linux系统编程的核心技术。通过本项目的学习和实践,可以深入理解:
- TCP Socket编程的原理和实践
- 多线程并发服务器的设计与实现
- JSON数据交换格式的应用
- SQLite数据库的使用
- 文件完整性校验和去重技术
文章到此结束啦,如果觉得本文章对您有所帮助,请点个赞和关注以及收藏,你的支持就是我继续更新的最大动力,谢谢!!!!