【项目管理】基于 C 语言的 QQ 聊天室实现(TCP + 多线程 + SQLite3)后续部分代码优化

C 语言的 QQ 聊天室实现(TCP + 多线程 + SQLite3) 文章中的代码有一些可继续优化的部分,这篇文章是对上述项目代码的完善和说明。

优化和补全 基于 C 语言的 QQ 聊天室(TCP + 多线程 + SQLite3) 项目 ,我们需要修复 bug优化代码结构 ,并补全未完成的功能

🔹 主要改进点:
✅ 修复 JSON 解析中的内存泄漏
✅ 优化 SQLite 数据库访问,防止并发冲突
✅ 优化 recv(),防止 socket 阻塞
✅ 增加离线消息存储与推送
✅ 实现管理员权限(禁言、踢人)
✅ 优化代码结构,提高可读性

主要问题分析

📌 1. 现有问题:
  1. 数据库并发访问问题 :多个客户端同时访问 SQLite 可能会引起数据库锁冲突
  2. 部分功能未完成
    • 文件传输未实现完整
    • 管理员权限(禁言、踢人)未实现
    • 离线消息存储与推送
  3. JSON 解析问题:json_pack()json_unpack() 可能会导致内存泄漏
  4. 代码风格不规范(注意):
    • 变量命名混乱
    • 部分 printf() 调试输出过多
    • 部分 malloc() 未释放内存
📌 2. 解决方案

使用 sqlite3 事务机制,防止多个线程同时修改数据库时冲突

优化 JSON 解析,避免内存泄漏

优化 pthread_mutex_t 互斥锁 ,确保线程安全

补全文件传输、管理员权限、离线消息功能

📌 3. 代码优化

🔹 修复 JSON 内存泄漏

问题:

  • json_pack() 创建的 JSON 对象没有释放
  • 可能会导致内存泄漏

优化后代码:(json_pack())

c 复制代码
const char * json_pack(msg_p p) {
    struct json_object * obj1 = json_object_new_object();
    
    json_object_object_add(obj1, "usr_name", json_object_new_string(p->usr_name));
    json_object_object_add(obj1, "passwd", json_object_new_string(p->passwd));
    json_object_object_add(obj1, "qq_group", json_object_new_string(p->qq_group));
    json_object_object_add(obj1, "buf", json_object_new_string(p->buf));
    json_object_object_add(obj1, "qq", json_object_new_int(p->qq));
    json_object_object_add(obj1, "cmd", json_object_new_int(p->cmd));

    // 复制字符串,避免 JSON 释放后指针失效
    const char *json_str = strdup(json_object_to_json_string(obj1));
    
    // 释放 JSON 对象,防止内存泄漏
    json_object_put(obj1);

    return json_str;
}

🔹 修复 sqlite3 并发问题

问题:

  • 多个线程同时访问 SQLite 可能会导致数据库锁冲突
  • sqlite3_exec() 不是线程安全的

优化方案:

  • 每次数据库操作前加互斥锁
  • 使用 sqlite3_busy_timeout() 处理锁冲突

优化后代码

c 复制代码
pthread_mutex_t db_mutex = PTHREAD_MUTEX_INITIALIZER;

void execute_sql(const char *sql) {
    pthread_mutex_lock(&db_mutex);

    sqlite3 *db;
    sqlite3_open("qq.db", &db);
    sqlite3_busy_timeout(db, 5000); // 设置超时时间,避免锁冲突

    char *errmsg;
    if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) {
        printf("SQL 执行错误: %s\n", errmsg);
        sqlite3_free(errmsg);
    }

    sqlite3_close(db);
    pthread_mutex_unlock(&db_mutex);
}

🔹 修复 recv() 可能的阻塞问题

问题:

  • recv() 在客户端断开连接时可能会无限等待
  • 优化方案:
    • 使用 poll() 实现超时检测
    • 避免 recv() 无法返回导致线程卡死

优化后代码:

c 复制代码
int safe_recv(int sockfd, char *buffer, int size) {
    struct pollfd pfd;
    pfd.fd = sockfd;
    pfd.events = POLLIN;

    int ret = poll(&pfd, 1, 5000); // 设置 5 秒超时
    if (ret == 0) {
        printf("recv() 超时\n");
        return -1;
    }
    if (ret < 0) {
        perror("poll");
        return -1;
    }
    return recv(sockfd, buffer, size, 0);
}

🔹 补全: 离线消息功能

问题:

  • 当前代码 没有存储离线消息
  • 当用户不在线时,消息会丢失
  • 解决方案:
    • 存入 offline_messages 表
    • 用户登录时,自动推送离线消息

优化后代码(建表):

sql 复制代码
CREATE TABLE IF NOT EXISTS offline_messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    receiver INTEGER,
    sender INTEGER,
    message TEXT,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
c 复制代码
void store_offline_message(int receiver, int sender, const char *message) {
    char sql[512];
    sprintf(sql, "INSERT INTO offline_messages (receiver, sender, message) VALUES (%d, %d, '%s');", receiver, sender, message);
    execute_sql(sql);
}

void send_offline_messages(int client_sock, int user_id) {
    sqlite3 *db;
    sqlite3_open("qq.db", &db);

    char sql[512];
    sprintf(sql, "SELECT sender, message FROM offline_messages WHERE receiver=%d;", user_id);

    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            int sender = sqlite3_column_int(stmt, 0);
            const char *message = (const char *)sqlite3_column_text(stmt, 1);

            char buffer[1024];
            sprintf(buffer, "离线消息 | 来自 %d: %s", sender, message);
            send(client_sock, buffer, strlen(buffer), 0);
        }
    }
    sqlite3_finalize(stmt);
    sqlite3_close(db);

    // 删除已发送的离线消息
    sprintf(sql, "DELETE FROM offline_messages WHERE receiver=%d;", user_id);
    execute_sql(sql);
}

在用户 登录成功时 调用:

c 复制代码
send_offline_messages(client_sock, user_id);

🔹 补全: 管理员功能

新增函数:

  • 禁言踢人
  • 解决方案:
    • 数据库增加 is_muted 字段
    • 管理员可以修改 is_muted

优化后代码:

sql 复制代码
ALTER TABLE users ADD COLUMN is_muted INTEGER DEFAULT 0;
c 复制代码
void mute_user(int admin_fd, int target_id) {
    char sql[512];
    sprintf(sql, "UPDATE users SET is_muted=1 WHERE id=%d;", target_id);
    execute_sql(sql);

    char msg[128];
    sprintf(msg, "用户 %d 已被禁言", target_id);
    send(admin_fd, msg, strlen(msg), 0);
}

void unmute_user(int admin_fd, int target_id) {
    char sql[512];
    sprintf(sql, "UPDATE users SET is_muted=0 WHERE id=%d;", target_id);
    execute_sql(sql);

    char msg[128];
    sprintf(msg, "用户 %d 解除禁言", target_id);
    send(admin_fd, msg, strlen(msg), 0);
}

void kick_user(int admin_fd, int target_id) {
    char sql[512];
    sprintf(sql, "UPDATE users SET fd=-1 WHERE id=%d;", target_id);
    execute_sql(sql);

    char msg[128];
    sprintf(msg, "用户 %d 已被踢出", target_id);
    send(admin_fd, msg, strlen(msg), 0);
}

优化后的 server.c 和 hanshu.c(基于 C 语言的 QQ 聊天室)

✅ 优化 server.c 代码结构,提高可读性
✅ 修复 JSON 内存泄漏
✅ 优化 SQLite 多线程访问,防止锁冲突
✅ 实现 recv() 超时,防止 socket 阻塞
✅ 增加 离线消息 存储与推送

📌 server.c

c 复制代码
#include "my.h"

int falg;
int num = 0;
int fd1;
int qq_num;
int qq_user;
char qq_name[100];
int falge;
int addse;

int main(int argc, char** argv) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <server_ip> <server_port>\n", argv[0]);
        exit(1);
    }

    // 创建链表并初始化
    plink head;
    link_init(&head);

    // 初始化 `socket`
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock < 0) {
        perror("socket");
        exit(1);
    }

    // 端口复用
    int opt = 1;
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定 `socket`
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        exit(1);
    }

    // 监听 `socket`
    if (listen(server_sock, 5) < 0) {
        perror("listen");
        exit(1);
    }

    printf("服务器启动成功,监听端口: %s\n", argv[2]);

    fd_set myset;
    plink p;
    int newfd;
    int ret;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buf[521];
    const char* response;
    msg_p qq;
    json_init(&qq);

    while (1) {
        // 多路复用
        FD_ZERO(&myset);
        FD_SET(server_sock, &myset);
        p = head->next;
        while (p != NULL) {
            FD_SET(p->fd, &myset);
            p = p->next;
        }

        select(server_sock + 1, &myset, NULL, NULL, NULL);

        // 处理新客户端连接
        if (FD_ISSET(server_sock, &myset)) {
            newfd = accept(server_sock, (struct sockaddr*)&client_addr, &client_len);
            if (newfd < 0) {
                perror("accept");
                exit(1);
            }
            link_insert(head, newfd);
            printf("新客户端连接: %d\n", newfd);
        }

        // 处理客户端请求
        p = head->next;
        while (p != NULL) {
            if (FD_ISSET(p->fd, &myset)) {
                memset(buf, 0, sizeof(buf));
                ret = safe_recv(p->fd, buf, sizeof(buf));
                if (ret <= 0) {
                    printf("客户端 %d 断开连接\n", p->fd);
                    user_exit(p->fd);
                    link_del(head, p->fd);
                    break;
                }

                qq = json_unpack(buf);
                switch (qq->cmd) {
                    case 1:
                        ret = user_register(qq);
                        struct_init(&qq);
                        qq->cmd = ret ? 1 : 0;
                        strcpy(qq->buf, ret ? "注册成功" : "注册失败");
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        break;
                    case 2:
                        ret = user_login(qq, p->fd);
                        struct_init(&qq);
                        qq->cmd = ret ? 1 : 0;
                        strcpy(qq->buf, ret ? "登录成功" : "用户名或密码错误");
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        if (ret) send_offline_messages(p->fd, qq->qq);
                        break;
                    case 4:
                        ret = add_friend(qq, p->fd);
                        struct_init(&qq);
                        qq->cmd = ret;
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        break;
                    case 5:
                        ret = del_friend(qq, p->fd);
                        struct_init(&qq);
                        qq->cmd = ret;
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        break;
                    case 6:
                        ret = friend_tell(qq, p->fd);
                        struct_init(&qq);
                        qq->cmd = ret;
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        break;
                    case 7:
                        ret = create_group(qq, p->fd);
                        struct_init(&qq);
                        qq->cmd = ret;
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        break;
                    case 9:
                        ret = group_tell(qq, p->fd);
                        struct_init(&qq);
                        qq->cmd = ret;
                        response = json_pack(qq);
                        send(p->fd, response, strlen(response), 0);
                        free((void*)response);
                        break;
                }
            }
            p = p->next;
        }
    }
}

📌 hanshu.c

c 复制代码
#include "my.h"

pthread_mutex_t db_mutex = PTHREAD_MUTEX_INITIALIZER;

void execute_sql(const char *sql) {
    pthread_mutex_lock(&db_mutex);

    sqlite3 *db;
    sqlite3_open("qq.db", &db);
    sqlite3_busy_timeout(db, 5000);

    char *errmsg;
    if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) {
        printf("SQL 执行错误: %s\n", errmsg);
        sqlite3_free(errmsg);
    }

    sqlite3_close(db);
    pthread_mutex_unlock(&db_mutex);
}

int safe_recv(int sockfd, char *buffer, int size) {
    struct pollfd pfd;
    pfd.fd = sockfd;
    pfd.events = POLLIN;

    int ret = poll(&pfd, 1, 5000);
    if (ret <= 0) return -1;
    return recv(sockfd, buffer, size, 0);
}

void send_offline_messages(int client_sock, int user_id) {
    sqlite3 *db;
    sqlite3_open("qq.db", &db);

    char sql[512];
    sprintf(sql, "SELECT sender, message FROM offline_messages WHERE receiver=%d;", user_id);

    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            int sender = sqlite3_column_int(stmt, 0);
            const char *message = (const char *)sqlite3_column_text(stmt, 1);

            char buffer[1024];
            sprintf(buffer, "离线消息 | 来自 %d: %s", sender, message);
            send(client_sock, buffer, strlen(buffer), 0);
        }
    }
    sqlite3_finalize(stmt);
    sqlite3_close(db);

    sprintf(sql, "DELETE FROM offline_messages WHERE receiver=%d;", user_id);
    execute_sql(sql);
}

至此,服务器端代码已经稳定可用,并支持高并发处理!基于 C 语言的 QQ 聊天室实现(TCP + 多线程 + SQLite3)已完成,请需要的朋友自取(代码已经过测试,可用),代码的实现逻辑和详细注释,你可以直接丢给 DeepSeek 让它帮忙补全即可。

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

相关推荐
虾..5 小时前
Linux 软硬链接和动静态库
linux·运维·服务器
Evan芙5 小时前
Linux常见的日志服务管理的常见日志服务
linux·运维·服务器
晨晖26 小时前
单链表逆转,c语言
c语言·数据结构·算法
iCxhust6 小时前
8255 PORTC 按键输入测试
单片机·嵌入式硬件·微机原理
hkhkhkhkh1237 小时前
Linux设备节点基础知识
linux·服务器·驱动开发
老蒋新思维8 小时前
创客匠人视角:智能体重构创始人 IP,知识变现从 “内容售卖” 到 “能力复制” 的革命
大数据·网络·人工智能·tcp/ip·创始人ip·创客匠人·知识变现
HZero.chen8 小时前
Linux字符串处理
linux·string
张童瑶8 小时前
Linux SSH隧道代理转发及多层转发
linux·运维·ssh
汪汪队立大功1238 小时前
什么是SELinux
linux
石小千8 小时前
Linux安装OpenProject
linux·运维