L-Chat——bug及修复

0 阅读3分钟

1、客户端输入行一直间断刷新L-Chat > L-Chat > L-Chat > L-Chat > L-Chat >

1.1 问题分析

关键:刚启动客户端就会隔大概几十秒刷新一个L-Chat >,无论输入还是不输入 应该跟心跳线程有关系

image.png

void* recv_thread_func(void* arg) {
else if (type == MSG_TYPE_PING){
            // 收到心跳回执,证明服务器还活着
            // 工业级应用中,这里通常用来计算网络延迟 (RTT),或者重置客户端自己的断线重连计时器
            // 这里选择“静默丢弃”,不打扰用户打字
            continue;
        }
        printf("L-Chat > "); // 保持提示符
        
        }
  • 心跳线程(第 52 行)向服务器发送的是 MSG_TYPE_PING(探活)。

  • 根据在服务端的协议定义,服务器收到 PING 后,给客户端回复的是 MSG_TYPE_PONG(确认回执)。

  • 当接收线程收到包时,解析出来的 typeMSG_TYPE_PONG

  • 但是代码写的是 if (type == MSG_TYPE_PING),这个条件显然不成立(False)

  • 于是程序地绕过了 continue;,直接到了最底部的 printf("L-Chat > ");

笔误

1.2解决方案

MSG_TYPE_PING 改成 MSG_TYPE_PONG

2、客户端1001给客户端1002发消息,1002未收到

2.1 问题分析

服务器

image.png

客户端1001

image.png

客户端1002

image.png

关键:Len:4294967287

chat_service.c

// 获取当前包的总长度,以此计算正文 content 的长度
    // 技巧:回溯包头获取 length 字段:payload 指针直接指向 conn->in_buf 缓冲区里的数据,而包头(Header)正好紧紧挨在它前面,所以指针往前退 sizeof(AppHeader) 刚好能读到完美的包头。
    AppHeader *header = (AppHeader *)((char *)payload - sizeof(AppHeader));//先转 char* 才能做指针算术
    uint32_t total_payload_len = ntohl(header->length);
    uint32_t content_len = total_payload_len - sizeof(ChatMsg);

  • 在 M3 阶段(单线程) :这招很好用。因为 payload 指针直接指向 conn->in_buf 缓冲区里的数据,而包头(Header)正好紧紧挨在它前面,所以指针往前退 sizeof(AppHeader) 刚好能读到完美的包头。
// ② 拷贝 payload 到堆内存
    if (payload_len > 0)
    {
        new_task->payload =malloc(payload_len);
        memcpy(new_task->payload,payload,payload_len);
    }else{
        new_task->payload = NULL;
    }

在 M4 阶段(线程池) :为了防止主线程 epoll 覆盖数据,在 thread_pool_submit 里用 mallocpayload 拷贝到了堆(Heap) 中。

  • 这时,payload 是一块全新的、干净的内存。
  • 此时再把指针往前退 sizeof(AppHeader),实际上是指向了未分配的野内存(垃圾数据)
  • 读取到的 header->length 是一个垃圾值(通常是 0)。
  • 接着计算正文长度 0 - 9 = -9。在 uint32_t(无符号 32 位整数)中,-9 溢出后正好就是 4294967287

混合使用有符号和无符号整数时要格外小心!小心内存溢出

  • 为什么 1002 收不到?

    • 代码最后执行了 send(target_fd, header, 12 + 4294967287, 0)
    • send(target_conn->fd, (char *)header, sizeof(AppHeader) + total_payload_len, 0);
    • 系统内核要发送 4.2 GB 的垃圾内存,直接报 EFAULT(地址越界)错误并拒绝发送。所以 1002 什么都收不到。

2.2 解决方案

既然 payload 已经是独立的内存了,我就不能再用指针回退的投机取巧方法,而是应该正经重新组装一个 Header

void handle_chat(Conn *conn, void *payload) {

    // ==========================================
    //【修复】: 抛弃指针回退,通过 strlen 动态计算真实长度
    // 正文长度 = 结构体大小 + 字符串长度 + 1(字符串结束符\0)
    uint32_t payload_len = sizeof(ChatMsg) + strlen(msg->content) + 1;
    // ==========================================



    if (type == 0) { // 私聊
            // ==========================================
            // 【修复】:在栈上重新组装一个完整的响应包
            char send_buf[1024]; 
            AppHeader *header = (AppHeader *)send_buf;
            header->magic = htonl(PROTOCOL_MAGIC);
            header->version = htons(1);
            header->type = htons(MSG_TYPE_CHAT_REQ);
            header->length = htonl(payload_len);
            
            // 把负载拷贝到 header 后面
            memcpy(send_buf + sizeof(AppHeader), payload, payload_len);
            
            // 发送完整的组装包
            send(target_conn->fd, send_buf, sizeof(AppHeader) + payload_len, 0);
            // ==========================================

}

2.2 测试结果

服务器

image.png

客户端1001

image.png

客户端1002

image.png