【项目管理】基于 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 让它帮忙补全即可。

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

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

相关推荐
格格Code2 分钟前
Tcp——客户端服务器
服务器·网络协议·tcp/ip
ALe要立志成为web糕手2 分钟前
用Python实现TCP代理
网络·python·网络协议·tcp/ip·安全·web安全
爱穿衬衫的张某某10 分钟前
websocket获取客服端真实ip
websocket·网络协议·tcp/ip
栗筝i39 分钟前
Spring 核心技术解析【纯干货版】- XVI:Spring 网络模块 Spring-WebMvc 模块精讲
java·网络·spring
辰熤✔44 分钟前
MQTT报文类型
c语言·网络
rkshangan1 小时前
软考高级:探寻易考科目与高效备考之路
大数据·网络
ORIPID1 小时前
Ubuntu完整复制其他用户的anaconda及虚拟环境
linux·运维·ubuntu
爱写代码的小朋友1 小时前
网络通信三大核心表项详解:MAC表、ARP表、路由表
网络
FreeBuf_1 小时前
Ubuntu 安全限制遭突破:攻击者可利用内核漏洞提权
linux·安全·ubuntu
色的归属感1 小时前
wireshark抓包分析数据怎么看 wireshark使用教程_wireshark怎么看
websocket·网络协议·tcp/ip·http·网络安全·https·udp