基于 C 语言的网络单词查询系统设计与实现(客户端 + 服务器端)
一、项目概述
本文将介绍一个基于 C 语言开发的网络单词查询系统,该系统包含客户端和服务器端两部分,支持用户注册、登录、单词查询及历史记录查询等功能。系统采用 TCP socket 实现网络通信,使用 SQLite 数据库存储用户信息、单词数据及查询记录,支持多客户端同时连接,具备完整的用户交互流程。
二、技术栈与开发环境
- 开发语言:C 语言
- 网络通信:TCP/IP socket 编程
- 数据库:SQLite3
- 多线程:pthread 库
- 开发环境:Linux(Ubuntu 20.04)
- 编译工具:gcc
三、系统架构设计
-
整体架构
采用客户端 - 服务器(C/S)架构,客户端负责用户交互和请求发送,服务器端负责处理并发请求、数据库操作和业务逻辑。 -
模块划分
- 客户端:连接管理、用户交互、消息发送 / 接收、命令处理
- 服务器端:socket 监听、客户端管理、线程池、数据库操作、业务逻辑处理
-
数据流程
客户端通过 TCP 连接发送命令(注册 / 登录 / 查询等),服务器端解析命令后执行对应操作(数据库读写),并将结果返回给客户端,客户端展示处理结果。
四、核心功能实现
1. 网络通信模块
- 客户端:使用
socket()
创建套接字,connect()
连接服务器,send()
/recv()
发送 / 接收数据,通过 pthread 实现消息接收线程 - 服务器端:
socket()
创建监听套接字,bind()
绑定端口,listen()
监听连接,accept()
接收客户端连接,为每个客户端创建独立线程处理请求。
2. 数据库模块
-
采用 SQLite3 实现数据持久化,设计 3 张核心表:
users
:存储用户名和密码(用户认证)words
:存储单词及释义(字典数据)history
:存储用户查询记录(带时间戳)
-
核心数据库操作:
- 用户注册 / 登录验证
- 单词查询与结果返回
- 查询历史记录的保存与读取
3. 多线程并发处理
- 服务器端为每个客户端连接创建独立线程,通过互斥锁(
pthread_mutex_t
)保护共享资源(客户端列表) - 实现多用户同时在线操作,互不干扰
4. 客户端交互界面
- 基于控制台的交互菜单,支持用户直观操作
- 实时显示连接状态和登录信息,提供清晰的操作反馈
5.服务器端源码
server.c
#include#include #include #include #include #include #include #include #include #define MAX_CLIENTS 100//宏定义最多有100个客户端连接#define BUFFER_SIZE 4096#define MAX_USERNAME 50#define MAX_PASSWORD 50#define MAX_WORD 100#define MAX_MEANING 500#define PORT 8888typedef struct { int socket; // 客户端套接字描述符 char username[MAX_USERNAME];// 客户端登录的用户名 int is_logged_in; // 登录状态(1表示已登录,0表示未登录) pthread_t thread; // 处理该客户端的线程ID} client_t;client_t clients[MAX_CLIENTS];//clients是client_t类型的结构体数组int client_count = 0;pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;sqlite3 *db;// 函数声明void *handle_client(void *arg);void remove_client(int client_socket);void broadcast_message(const char *message, int exclude_socket);int init_database();int import_dict_data();int register_user(const char *username, const char *password);int login_user(const char *username, const char *password);int logout_user(const char *username);int search_word(const char *word, char *meaning);int save_history(const char *username, const char *word, const char *meaning);int get_history(const char *username, char *history, size_t max_len);void signal_handler(int sig);void signal_handler(int sig) { printf(\"\\n服务器收到中断信号,正在清理资源...\\n\"); pthread_mutex_lock(&clients_mutex); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1) { close(clients[i].socket); } } pthread_mutex_unlock(&clients_mutex); sqlite3_close(db); printf(\"服务器已退出\\n\"); exit(0);}int init_database() { int rc = sqlite3_open(\"word_database.db\", &db); if (rc != SQLITE_OK) { printf(\"数据库打开失败: %s\\n\", sqlite3_errmsg(db)); return 0; } const char *create_users = \"CREATE TABLE IF NOT EXISTS users (\" \"id INTEGER PRIMARY KEY AUTOINCREMENT,\" \"username TEXT UNIQUE NOT NULL,\" \"password TEXT NOT NULL\" \");\"; const char *create_words = \"CREATE TABLE IF NOT EXISTS words (\" \"id INTEGER PRIMARY KEY AUTOINCREMENT,\" \"word TEXT UNIQUE NOT NULL,\" \"meaning TEXT NOT NULL\" \");\"; const char *create_history = \"CREATE TABLE IF NOT EXISTS history (\" \"id INTEGER PRIMARY KEY AUTOINCREMENT,\" \"username TEXT NOT NULL,\" \"word TEXT NOT NULL,\" \"meaning TEXT NOT NULL,\" \"query_time TIMESTAMP DEFAULTFAULT CURRENT_TIMESTAMP\" \");\"; char *err_msg = NULL; if (sqlite3_exec(db, create_users, NULL, NULL, &err_msg) != SQLITE_OK) { printf(\"创建用户表失败: %s\\n\", err_msg); sqlite3_free(err_msg); return 0; } if (sqlite3_exec(db, create_words, NULL, NULL, &err_msg) != SQLITE_OK) { printf(\"创建单词表失败: %s\\n\", err_msg); sqlite3_free(err_msg); return 0; } if (sqlite3_exec(db, create_history, NULL, NULL, &err_msg) != SQLITE_OK) { printf(\"创建历史记录表失败: %s\\n\", err_msg); sqlite3_free(err_msg); return 0; } printf(\"数据库初始化成功\\n\"); return 1;}int import_dict_data() { FILE *file = fopen(\"dict.txt\", \"r\"); if (!file) { printf(\"dict.txt文件不存在!请在服务器目录创建该文件\\n\"); return 0; } char line[BUFFER_SIZE]; char word[MAX_WORD]; char meaning[MAX_MEANING]; int count = 0;// 定义SQL插入语句,使用INSERT OR IGNORE:当插入的word已存在时忽略此次插入(避免重复)// ?是参数占位符,后续通过绑定函数设置具体值,防止SQL注入并提高效率 const char *insert_sql = \"INSERT OR IGNORE INTO words (word, meaning) VALUES (?, ?);\"; sqlite3_stmt *stmt;//结构体类型 //sqlite3_prepare_v2 是 SQLite 库提供的一个函数,用于将 SQL 语句编译(预处理)为二进制格式的准备语句(prepared statement),以便后续高效执行。 if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL) != SQLITE_OK) { printf(\"SQL预处理失败: %s\\n\", sqlite3_errmsg(db)); fclose(file); return 0; } while (fgets(line, sizeof(line), file)) { line[strcspn(line, \"\\n\")] = \'\\0\'; char *tab_pos = strchr(line, \'\\t\'); if (!tab_pos) continue; *tab_pos = \'\\0\'; strncpy(word, line, MAX_WORD - 1); strncpy(meaning, tab_pos + 1, MAX_MEANING - 1); sqlite3_bind_text(stmt, 1, word, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, meaning, -1, SQLITE_STATIC); if (sqlite3_step(stmt) == SQLITE_DONE) { count++; } sqlite3_reset(stmt); } sqlite3_finalize(stmt); fclose(file); printf(\"成功导入 %d 个单词\\n\", count); return 1;}int register_user(const char *username, const char *password) { // 修复:确保用户名和密码不包含特殊字符 if (strchr(username, \'|\') || strchr(username, \' \') || strchr(password, \'|\') || strchr(password, \' \')) { return 0; } const char *insert_sql = \"INSERT INTO users (username, password) VALUES (?, ?);\"; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL) != SQLITE_OK) { printf(\"注册SQL预处理失败: %s\\n\", sqlite3_errmsg(db)); return 0; } sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC); int result = (sqlite3_step(stmt) == SQLITE_DONE) ? 1 : 0; sqlite3_finalize(stmt); return result;}// 修复:登录验证逻辑,确保正确比对密码int login_user(const char *username, const char *password) { const char *query_sql = \"SELECT password FROM users WHERE username = ?;\"; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, query_sql, -1, &stmt, NULL) != SQLITE_OK) { printf(\"登录SQL预处理失败: %s\\n\", sqlite3_errmsg(db)); return 0; } sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); int rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); return 0; // 用户名不存在 } // 修复:正确获取数据库中的密码并比对 const char *stored_pwd = (const char *)sqlite3_column_text(stmt, 0); int result = (strcmp(stored_pwd, password) == 0) ? 1 : 0; sqlite3_finalize(stmt); return result;}int search_word(const char *word, char *meaning) { const char *query_sql = \"SELECT meaning FROM words WHERE word = ?;\"; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, query_sql, -1, &stmt, NULL) != SQLITE_OK) { printf(\"查询SQL预处理失败: %s\\n\", sqlite3_errmsg(db)); return 0; } sqlite3_bind_text(stmt, 1, word, -1, SQLITE_STATIC); int rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); return 0; } const char *result_meaning = (const char *)sqlite3_column_text(stmt, 0); strncpy(meaning, result_meaning, MAX_MEANING - 1); sqlite3_finalize(stmt); return 1;}int save_history(const char *username, const char *word, const char *meaning) { const char *insert_sql = \"INSERT INTO history (username, word, meaning) VALUES (?, ?, ?);\"; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL) != SQLITE_OK) { printf(\"历史记录SQL预处理失败: %s\\n\", sqlite3_errmsg(db)); return 0; } sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, word, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, meaning, -1, SQLITE_STATIC); int result = (sqlite3_step(stmt) == SQLITE_DONE) ? 1 : 0; sqlite3_finalize(stmt); return result;}int get_history(const char *username, char *history, size_t max_len) { const char *query_sql = \"SELECT word, meaning, query_time FROM history \" \"WHERE username = ? ORDER BY query_time DESC LIMIT 10;\"; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, query_sql, -1, &stmt, NULL) != SQLITE_OK) { printf(\"历史查询SQL预处理失败: %s\\n\", sqlite3_errmsg(db)); return 0; } sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); memset(history, 0, max_len); strncat(history, \"=== 查询历史 ===\\n\", max_len - strlen(history) - 1); int count = 0; while (sqlite3_step(stmt) == SQLITE_ROW && strlen(history) < max_len - 100) { const char *word = (const char *)sqlite3_column_text(stmt, 0); const char *meaning = (const char *)sqlite3_column_text(stmt, 1); const char *time = (const char *)sqlite3_column_text(stmt, 2); char line[200]; snprintf(line, sizeof(line), \"%d. %s - %s [%s]\\n\", ++count, word, meaning, time); strncat(history, line, max_len - strlen(history) - 1); } if (count == 0) { strncat(history, \"暂无查询记录\\n\", max_len - strlen(history) - 1); } sqlite3_finalize(stmt); return count;}void broadcast_message(const char *message, int exclude_socket) { pthread_mutex_lock(&clients_mutex); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && clients[i].is_logged_in && clients[i].socket != exclude_socket) { send(clients[i].socket, message, strlen(message), 0); } } pthread_mutex_unlock(&clients_mutex);}void remove_client(int client_socket) { pthread_mutex_lock(&clients_mutex); for (int i = 0; i socket); while (1) { memset(buffer, 0, BUFFER_SIZE); int recv_len = recv(client->socket, buffer, BUFFER_SIZE - 1, 0); if (recv_len socket); break; } buffer[recv_len] = \'\\0\'; printf(\"客户端 %d 命令: %s\\n\", client->socket, buffer); char *command = strtok(buffer, \" \"); char *params = strtok(NULL, \"\"); if (!command) continue; // 处理注册命令 if (strcmp(command, \"REGISTER\") == 0) { if (!params) { strcpy(response, \"ERROR: 注册格式错误,正确格式: REGISTER 用户名|密码\"); send(client->socket, response, strlen(response), 0); continue; } char *user = strtok(params, \"|\"); char *pwd = strtok(NULL, \"|\"); if (!user || !pwd) { strcpy(response, \"ERROR: 注册格式错误,正确格式: REGISTER 用户名|密码\"); send(client->socket, response, strlen(response), 0); continue; } // 修复:检查用户名和密码长度 if (strlen(user) >= MAX_USERNAME || strlen(pwd) >= MAX_PASSWORD) { strcpy(response, \"ERROR: 用户名或密码过长\"); send(client->socket, response, strlen(response), 0); continue; } if (register_user(user, pwd)) { snprintf(response, sizeof(response), \"SUCCESS: 注册成功,用户名: %s\", user); } else { strcpy(response, \"ERROR: 注册失败,用户名已存在或包含特殊字符\"); } send(client->socket, response, strlen(response), 0); // 处理登录命令 } else if (strcmp(command, \"LOGIN\") == 0) { if (client->is_logged_in) { strcpy(response, \"ERROR: 已登录,无需重复登录\"); send(client->socket, response, strlen(response), 0); continue; } if (!params) { strcpy(response, \"ERROR: 登录格式错误,正确格式: LOGIN 用户名|密码\"); send(client->socket, response, strlen(response), 0); continue; } char *user = strtok(params, \"|\"); char *pwd = strtok(NULL, \"|\"); if (!user || !pwd) { strcpy(response, \"ERROR: 登录格式错误,正确格式: LOGIN 用户名|密码\"); send(client->socket, response, strlen(response), 0); continue; } // 修复:验证登录逻辑 if (login_user(user, pwd)) { // 检查是否已在其他地方登录 pthread_mutex_lock(&clients_mutex); int already_login = 0; for (int i = 0; i socket, response, strlen(response), 0); continue; } // 登录成功 strncpy(client->username, user, MAX_USERNAME - 1); client->is_logged_in = 1; pthread_mutex_unlock(&clients_mutex); snprintf(response, sizeof(response), \"SUCCESS: 登录成功,欢迎 %s\", user); send(client->socket, response, strlen(response), 0); char broadcast_msg[100]; snprintf(broadcast_msg, sizeof(broadcast_msg), \"SYSTEM: 用户 %s 已上线\", user); broadcast_message(broadcast_msg, client->socket); } else { strcpy(response, \"ERROR: 登录失败,用户名或密码错误\"); send(client->socket, response, strlen(response), 0); } // 处理登出命令 } else if (strcmp(command, \"LOGOUT\") == 0) { if (!client->is_logged_in) { strcpy(response, \"ERROR: 未登录,无需登出\"); send(client->socket, response, strlen(response), 0); continue; } char username[MAX_USERNAME]; strncpy(username, client->username, MAX_USERNAME); pthread_mutex_lock(&clients_mutex); client->is_logged_in = 0; memset(client->username, 0, MAX_USERNAME); pthread_mutex_unlock(&clients_mutex); strcpy(response, \"SUCCESS: 登出成功\"); send(client->socket, response, strlen(response), 0); char broadcast_msg[100]; snprintf(broadcast_msg, sizeof(broadcast_msg), \"SYSTEM: 用户 %s 已下线\", username); broadcast_message(broadcast_msg, client->socket); // 处理单词查询 } else if (strcmp(command, \"SEARCH\") == 0) { if (!client->is_logged_in) { strcpy(response, \"ERROR: 请先登录\"); send(client->socket, response, strlen(response), 0); continue; } if (!params) { strcpy(response, \"ERROR: 请输入要查询的单词\"); send(client->socket, response, strlen(response), 0); continue; } char meaning[MAX_MEANING]; if (search_word(params, meaning)) { snprintf(response, sizeof(response), \"SUCCESS: %s - %s\", params, meaning); save_history(client->username, params, meaning); } else { snprintf(response, sizeof(response), \"ERROR: 未找到单词 %s\", params); } send(client->socket, response, strlen(response), 0); // 处理历史记录查询 } else if (strcmp(command, \"HISTORY\") == 0) { if (!client->is_logged_in) { strcpy(response, \"ERROR: 请先登录\"); send(client->socket, response, strlen(response), 0); continue; } // 修复:预留足够空间给前缀 char history[BUFFER_SIZE - 20]; // 预留20字节给\"SUCCESS: \"前缀 int count = get_history(client->username, history, sizeof(history)); if (count > 0) { snprintf(response, BUFFER_SIZE, \"SUCCESS: %s\", history); } else { strcpy(response, \"SUCCESS: 暂无查询历史\"); } send(client->socket, response, strlen(response), 0); // 处理退出命令 } else if (strcmp(command, \"QUIT\") == 0) { strcpy(response, \"SUCCESS: 正在断开连接\"); send(client->socket, response, strlen(response), 0); break; } else { strcpy(response, \"ERROR: 未知命令\"); send(client->socket, response, strlen(response), 0); } } remove_client(client->socket); return NULL;}int main() { int server_socket, client_socket; struct sockaddr_in server_addr, client_addr;//创建服务器和客户端绑定信息的结构体 socklen_t client_len = sizeof(client_addr); // 初始化信号处理 signal(SIGINT, signal_handler); // 初始化数据库 if (!init_database()) { printf(\"数据库初始化失败,无法启动服务器\\n\"); return 1; } // 导入字典数据 import_dict_data(); // 即使导入失败也继续运行服务器 // 创建socket server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { perror(\"Socket创建失败\"); exit(EXIT_FAILURE); } // 设置socket选项 int opt = 1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//实现端口的快速复用 // 配置服务器地址并绑定 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror(\"绑定失败\"); exit(EXIT_FAILURE); } // 监听连接 if (listen(server_socket, 5) < 0) { perror(\"监听失败\"); exit(EXIT_FAILURE); } printf(\"服务器启动成功,监听端口 %d...\\n\", PORT); // 初始化客户端数组 for (int i = 0; i < MAX_CLIENTS; i++) { clients[i].socket = -1; clients[i].is_logged_in = 0; memset(clients[i].username, 0, MAX_USERNAME); } // 接受客户端连接 while ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len))) { pthread_mutex_lock(&clients_mutex); // 查找空闲位置 int i; for (i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket == -1) { clients[i].socket = client_socket; clients[i].is_logged_in = 0; client_count++; break; } } pthread_mutex_unlock(&clients_mutex); if (i == MAX_CLIENTS) { printf(\"达到最大客户端数量限制\\n\"); close(client_socket); continue; } // 创建线程处理客户端 if (pthread_create(&clients[i].thread, NULL, handle_client, &clients[i]) != 0) { perror(\"创建线程失败\"); remove_client(client_socket); } else { printf(\"客户端 %d 连接成功,当前在线数: %d\\n\", client_socket, client_count); } } close(server_socket); sqlite3_close(db); return 0;}
6.客户端代码
main.c
#include #include #include #include \"fun.h\"#define MAX_INPUT 1024#define MAX_USERNAME 50#define MAX_PASSWORD 50// 函数声明void show_menu();void handle_connection();void handle_register();void handle_login();void handle_logout();void handle_search();void handle_history();void clear_screen();void show_status();int main() { int choice; char input[MAX_INPUT]; printf(\"=== 单词查询系统 ===\\n\"); printf(\"支持用户注册、登录、单词查询及历史记录功能\\n\\n\"); while (1) { show_status(); show_menu(); printf(\"请选择操作 (1-7): \"); if (fgets(input, MAX_INPUT, stdin) == NULL) { break; } choice = atoi(input); switch (choice) { case 1: handle_connection(); break; case 2: handle_register(); break; case 3: handle_login(); break; case 4: handle_search(); break; case 5: handle_history(); break; case 6: handle_logout(); break; case 7: printf(\"正在退出程序...\\n\"); disconnect_from_server(); return 0; default: printf(\"无效的选择,请重新输入\\n\"); break; } printf(\"\\n按回车键继续...\"); fgets(input, MAX_INPUT, stdin); clear_screen(); } return 0;}// 显示主菜单void show_menu() { printf(\"\\n=== 功能菜单 ===\\n\"); printf(\"1. 连接到服务器\\n\"); printf(\"2. 用户注册\\n\"); printf(\"3. 用户登录\\n\"); printf(\"4. 查询单词\\n\"); printf(\"5. 查看查询历史\\n\"); printf(\"6. 用户登出\\n\"); printf(\"7. 退出程序\\n\"); printf(\"================\\n\");}// 显示当前状态void show_status() { printf(\"\\n=== 当前状态 ===\\n\"); if (is_connected()) { printf(\"🔗 连接状态: 已连接到服务器\\n\"); } else { printf(\"🔗 连接状态: 未连接到服务器\\n\"); } if (is_logged_in()) { printf(\"👤 登录状态: 已登录 (%s)\\n\", get_username()); } else { printf(\"👤 登录状态: 未登录\\n\"); } printf(\"================\\n\");}// 处理服务器连接void handle_connection() { char server_ip[100]; char port_str[10]; int port; if (is_connected()) { printf(\"已经连接到服务器,无需重复连接\\n\"); return; } printf(\"请输入服务器IP地址 (默认: 127.0.0.1): \"); fgets(server_ip, sizeof(server_ip), stdin); server_ip[strcspn(server_ip, \"\\n\")] = 0; if (strlen(server_ip) == 0) { strcpy(server_ip, \"127.0.0.1\"); } printf(\"请输入端口号 (默认: 8888): \"); fgets(port_str, sizeof(port_str), stdin); port_str[strcspn(port_str, \"\\n\")] = 0; if (strlen(port_str) == 0) { port = 8888; } else { port = atoi(port_str); } printf(\"正在连接到服务器 %s:%d...\\n\", server_ip, port); if (connect_to_server(server_ip, port)) { printf(\"连接成功!\\n\"); } else { printf(\"连接失败!\\n\"); }}// 处理用户注册void handle_register() { char username[MAX_USERNAME];//用户名 char password[MAX_PASSWORD];//首次输入的密码 char confirm_password[MAX_PASSWORD];//再次输入的密码 if (!is_connected()) { printf(\"请先连接到服务器\\n\"); return; } if (is_logged_in()) { printf(\"您已登录,请先登出再注册新用户\\n\"); return; } printf(\"请输入用户名: \"); fgets(username, sizeof(username), stdin); username[strcspn(username, \"\\n\")] = 0; if (strlen(username) == 0) { printf(\"用户名不能为空\\n\"); return; } printf(\"请输入密码: \"); fgets(password, sizeof(password), stdin); password[strcspn(password, \"\\n\")] = 0; if (strlen(password) == 0) { printf(\"密码不能为空\\n\"); return; } printf(\"请确认密码: \"); fgets(confirm_password, sizeof(confirm_password), stdin); confirm_password[strcspn(confirm_password, \"\\n\")] = 0; if (strcmp(password, confirm_password) != 0) {//判断两次输入的密码是否一致 printf(\"两次输入的密码不一致\\n\"); return;//返回操作主页并清屏 } printf(\"正在注册用户 %s...\\n\", username); if (register_user(username, password)) { printf(\"注册请求已发送,请等待服务器响应...\\n\"); } else { printf(\"注册请求发送失败\\n\"); }}// 处理用户登录void handle_login() { char username[MAX_USERNAME]; char password[MAX_PASSWORD]; if (!is_connected()) { printf(\"请先连接到服务器\\n\"); return; } if (is_logged_in()) { printf(\"您已登录,无需重复登录\\n\"); return; } printf(\"请输入用户名: \"); fgets(username, sizeof(username), stdin); username[strcspn(username, \"\\n\")] = 0; if (strlen(username) == 0) { printf(\"用户名不能为空\\n\"); return; } printf(\"请输入密码: \"); fgets(password, sizeof(password), stdin); password[strcspn(password, \"\\n\")] = 0; if (strlen(password) == 0) { printf(\"密码不能为空\\n\"); return; } printf(\"正在登录用户 %s...\\n\", username); if (login_user(username, password)) { printf(\"登录请求已发送,请等待服务器响应...\\n\"); } else { printf(\"登录请求发送失败\\n\"); }}// 处理用户登出void handle_logout() { if (!is_connected()) { printf(\"未连接到服务器\\n\"); return; } if (!is_logged_in()) { printf(\"您尚未登录,无需登出\\n\"); return; } printf(\"正在登出用户 %s...\\n\", get_username()); if (logout_user()) { printf(\"登出请求已发送,请等待服务器响应...\\n\"); } else { printf(\"登出请求发送失败\\n\"); }}// 处理单词查询void handle_search() { char word[100]; if (!is_connected()) { printf(\"请先连接到服务器\\n\"); return; } if (!is_logged_in()) { printf(\"请先登录\\n\"); return; } printf(\"请输入要查询的单词: \"); fgets(word, sizeof(word), stdin); word[strcspn(word, \"\\n\")] = 0; if (strlen(word) == 0) { printf(\"单词不能为空\\n\"); return; } printf(\"正在查询单词 %s...\\n\", word); if (search_word(word)) { printf(\"查询请求已发送,请等待服务器响应...\\n\"); } else { printf(\"查询请求发送失败\\n\"); }}// 处理历史记录查询void handle_history() { if (!is_connected()) { printf(\"请先连接到服务器\\n\"); return; } if (!is_logged_in()) { printf(\"请先登录\\n\"); return; } printf(\"正在获取查询历史记录...\\n\"); if (get_history()) { printf(\"历史记录请求已发送,请等待服务器响应...\\n\"); } else { printf(\"历史记录请求发送失败\\n\"); }}// 清屏函数(跨平台支持)void clear_screen() {#ifdef _WIN32 system(\"cls\");#else system(\"clear\");#endif}
fun.c
#include #include #include #include #include #include #include #include #include #include \"fun.h\"#define BUFFER_SIZE 1024#define MAX_USERNAME 50// 客户端信息结构体typedef struct { int socket; char username[MAX_USERNAME]; int is_connected; int is_logged_in; pthread_t receive_thread;} client_info_t;client_info_t client_info = {0};// 函数声明void *receive_messages(void *arg);void handle_server_response(const char *response);void signal_handler(int sig);// 连接到服务器int connect_to_server(const char *server_ip, int port) { // 如果已经连接,先断开 if (client_info.is_connected) { disconnect_from_server(); } // 创建socket client_info.socket = socket(AF_INET, SOCK_STREAM, 0); if (client_info.socket == -1) { printf(\"创建socket失败\\n\"); return 0; } // 设置服务器地址 struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); // 转换IP地址 if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) { printf(\"无效的IP地址\\n\"); close(client_info.socket); return 0; } // 连接服务器 if (connect(client_info.socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { printf(\"连接服务器失败\\n\"); close(client_info.socket); return 0; } // 初始化客户端信息 client_info.is_connected = 1; client_info.is_logged_in = 0; memset(client_info.username, 0, MAX_USERNAME); // 设置信号处理 signal(SIGINT, signal_handler); // 创建接收消息线程 if (pthread_create(&client_info.receive_thread, NULL, receive_messages, NULL) != 0) { printf(\"创建接收线程失败\\n\"); close(client_info.socket); client_info.is_connected = 0; return 0; } printf(\"成功连接到服务器 %s:%d\\n\", server_ip, port); return 1;}// 发送消息到服务器int send_message(const char *message) { if (!client_info.is_connected) { printf(\"未连接到服务器\\n\"); return 0; } int bytes_sent = send(client_info.socket, message, strlen(message), 0); if (bytes_sent == -1) { printf(\"发送消息失败\\n\"); return 0; } return 1;}// 注册用户int register_user(const char *username, const char *password) { if (!client_info.is_connected) { printf(\"未连接到服务器\\n\"); return 0; } char message[BUFFER_SIZE]; snprintf(message, BUFFER_SIZE, \"REGISTER %s|%s\", username, password); return send_message(message);}// 用户登录int login_user(const char *username, const char *password) { if (!client_info.is_connected) { printf(\"未连接到服务器\\n\"); return 0; } char message[BUFFER_SIZE]; snprintf(message, BUFFER_SIZE, \"LOGIN %s|%s\", username, password); if (send_message(message)) { // 先暂存用户名,等待服务器验证成功后再确认登录状态 strncpy(client_info.username, username, MAX_USERNAME - 1); return 1; } return 0;}// 用户登出int logout_user() { if (!client_info.is_connected) { printf(\"未连接到服务器\\n\"); return 0; } if (!client_info.is_logged_in) { printf(\"您尚未登录\\n\"); return 0; } if (send_message(\"LOGOUT\")) { client_info.is_logged_in = 0; return 1; } return 0;}// 查询单词int search_word(const char *word) { if (!client_info.is_connected) { printf(\"未连接到服务器\\n\"); return 0; } if (!client_info.is_logged_in) { printf(\"请先登录\\n\"); return 0; } char message[BUFFER_SIZE]; snprintf(message, BUFFER_SIZE, \"SEARCH %s\", word); return send_message(message);}// 获取历史记录int get_history() { if (!client_info.is_connected) { printf(\"未连接到服务器\\n\"); return 0; } if (!client_info.is_logged_in) { printf(\"请先登录\\n\"); return 0; } return send_message(\"HISTORY\");}// 断开与服务器的连接void disconnect_from_server() { if (client_info.is_connected) { // 如果已登录,先发送登出命令 if (client_info.is_logged_in) { send_message(\"LOGOUT\"); sleep(1); // 等待登出命令处理 } // 发送退出命令 send_message(\"QUIT\"); // 等待接收线程结束 pthread_join(client_info.receive_thread, NULL); // 关闭socket close(client_info.socket); // 重置客户端信息 client_info.is_connected = 0; client_info.is_logged_in = 0; memset(client_info.username, 0, MAX_USERNAME); printf(\"已断开与服务器的连接\\n\"); }}// 信号处理函数void signal_handler(int sig) { printf(\"\\n收到中断信号,正在退出...\\n\"); disconnect_from_server(); exit(0);}// 接收服务器消息的线程void *receive_messages(void *arg) { char buffer[BUFFER_SIZE]; while (client_info.is_connected) { memset(buffer, 0, BUFFER_SIZE); int bytes_received = recv(client_info.socket, buffer, BUFFER_SIZE - 1, 0); if (bytes_received <= 0) { printf(\"与服务器的连接已断开\\n\"); client_info.is_connected = 0; client_info.is_logged_in = 0; break; } buffer[bytes_received] = \'\\0\'; handle_server_response(buffer); } return NULL;}// 处理服务器响应void handle_server_response(const char *response) { if (strncmp(response, \"SUCCESS:\", 8) == 0) { printf(\"✅ %s\\n\", response + 8); // 处理登录成功的情况 if (strstr(response, \"登录成功\") != NULL) { client_info.is_logged_in = 1; } } else if (strncmp(response, \"ERROR:\", 6) == 0) { printf(\"❌ %s\\n\", response + 6); // 处理登录失败的情况 if (strstr(response, \"登录失败\") != NULL) { memset(client_info.username, 0, MAX_USERNAME); } } else if (strncmp(response, \"SYSTEM:\", 7) == 0) { printf(\"📢 %s\\n\", response + 7); } else { printf(\"收到消息: %s\\n\", response); }}// 检查是否已连接到服务器int is_connected() { return client_info.is_connected;}// 检查是否已登录int is_logged_in() { return client_info.is_logged_in;}// 获取当前用户名const char* get_username() { return client_info.username;}
fun.h
#ifndef FUN_H#define FUN_H#include // 函数声明int connect_to_server(const char *server_ip, int port);int send_message(const char *message);int register_user(const char *username, const char *password);int login_user(const char *username, const char *password);int logout_user();int search_word(const char *word);int get_history();void disconnect_from_server();int is_connected();int is_logged_in();const char* get_username();#endif // FUN_H
五、系统测试与运行效果
-
环境搭建
- 服务器端:编译时需链接
-lsqlite3 -lpthread
库,准备dict.txt
字典文件(格式:单词 \\t 释义) - 客户端:直接编译即可,运行时输入服务器 IP 和端口连接
- 服务器端:编译时需链接
-
操作流程演示
- 连接服务器 → 注册 / 登录 → 查询单词 → 查看历史 → 登出 / 退出
-
核心功能测试
- 多客户端并发连接测试
- 用户注册与登录验证
- 单词查询响应速度
- 历史记录存储与展示