You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言多线程客户端-服务器异常:请求重复与丢失问题排查

客户端-服务器请求重复/丢失问题排查与解决

问题背景

用C语言实现了带有4个工作线程的客户端-服务器程序,采用生产者消费者模型的队列缓冲请求。启动服务器命令为./wserver -p 8003,通过测试脚本multiple_clients.bash发起15个并发请求时,出现**部分请求重复(如test13.html、test11.html)、部分请求丢失(如test7.html、test14.html)**的异常:总输出数量为15但内容不符,程序无double free崩溃,但存在单个请求被重复处理的情况。

核心代码

request.c

struct conn_args
{
  int fd;
  int filename;  // 类型错误:应为char*
  int filesize;
  struct conn_args *next;
}

/* other functions */

int add(int fd, char *filename, int filesize) 
{
    if(buffer_size >= buffer_max_size)
       return 1;
    
    struct conn_args *temp = malloc(sizeof(struct conn_args));

    temp->fd = fd;
    temp->filename = filename;  // 直接保存指针,未复制字符串内容
    temp->filesize = filesize;
    temp->next = NULL;

    if (head == NULL) 
    {
       head = temp;
       tail = temp;
    } 
    else 
    {
      tail->next = temp;
      tail = temp;
    }
    buffer_size++;
    return 0;
}

struct conn_args* get_args()
{
  struct conn_args *temp = NULL;
  if(head!=NULL)
  {
    temp = head;
    head = head->next;
    buffer_size--;
  }
  return temp;
}

void *thread_request_serve_static(void *arg) 
{
  while (1) 
  {
    pthread_mutex_lock(&lock);
    struct conn_args *arg = get_args();

    if (!arg)
      pthread_cond_wait(&c, &lock);
    else 
    {
      request_serve_static(arg->fd, arg->filename, arg->filesize);
      printf("Request for %s is removed from the buffer\n-----------------\n",
             arg->filename);
      close_or_die(arg->fd);
      free(arg);
    }
    pthread_mutex_unlock(&lock);
  }
}

void request_handle(int fd) 
{
 /* variable declarations and condition checks */

 pthread_mutex_lock(&lock);
 int stat = add(fd, filename, sbuf.st_size);
 pthread_mutex_unlock(&lock);

 if (!stat) 
 {
    printf("-----------------\nRequest for %s is added to the buffer\n",
      filename);
    pthread_cond_signal(&c);
 } 
 else 
 {
    printf("Server overload: Try again later!");
 }
}

wserver.c

int main(int argc, char *argv[])
{
   /* variable initializations and argument reading */

pthread_t thread_pool[num_threads];
for(int i=0; i<num_threads; i++)
    pthread_create(&thread_pool[i], NULL, thread_request_serve_static, NULL);

// open the socket connection
int listen_fd = open_listen_fd_or_die(port);
while (1) {
    
    struct sockaddr_in client_addr;
    int client_len = sizeof(client_addr);
    int conn_fd = accept_or_die(listen_fd, (sockaddr_t *) &client_addr, (socklen_t *) &client_len);
    
    request_handle(conn_fd);
}
}

测试脚本 multiple_clients.bash

echo Starting requests

for ((i=15;i>0;i-=1))
do
    ./wclient localhost 8003 /files/test$i.html &
done

echo Requests completed

异常原因分析

  1. 结构体成员类型错误
    struct conn_args中的filename被错误定义为int类型(实际应为char*),在64位系统下,char*占8字节,int仅占4字节,会导致指针被截断,存储的地址无效或指向错误内存区域,最终显示混乱的文件名。

  2. 字符串指针未复制,内存被复用覆盖
    add函数中直接将传入的filename指针赋值给temp->filename,未复制字符串内容。如果request_handle中的filename是栈上变量或被复用的缓冲区,后续请求会覆盖该内存,队列中已存在的conn_args的filename会指向新的字符串,导致输出时出现重复文件名。

  3. 条件变量使用逻辑缺陷
    工作线程中,当队列空时调用pthread_cond_wait,但被唤醒后未重新检查队列状态,可能导致队列中的请求未被及时处理;同时未处理虚假唤醒,存在线程漏处理请求的风险。

解决方法

1. 修正结构体成员类型

将struct conn_args中的filename改为char*类型:

struct conn_args
{
  int fd;
  char *filename;
  int filesize;
  struct conn_args *next;
};

2. 复制文件名内容,避免指针共享

在add函数中,使用strdup分配内存并复制字符串内容:

int add(int fd, char *filename, int filesize) 
{
    if(buffer_size >= buffer_max_size)
       return 1;
    
    struct conn_args *temp = malloc(sizeof(struct conn_args));
    if (!temp) return 1;

    temp->fd = fd;
    // 复制文件名,避免指向栈内存或复用缓冲区
    temp->filename = strdup(filename);
    if (!temp->filename) {
        free(temp);
        return 1;
    }
    temp->filesize = filesize;
    temp->next = NULL;

    if (head == NULL) 
    {
       head = temp;
       tail = temp;
    } 
    else 
    {
      tail->next = temp;
      tail = temp;
    }
    buffer_size++;
    return 0;
}

3. 修正工作线程的条件变量逻辑

使用循环检查队列状态,避免虚假唤醒,同时处理请求时释放锁以提升性能:

void *thread_request_serve_static(void *arg) 
{
  while (1) 
  {
    pthread_mutex_lock(&lock);
    // 循环检查队列是否为空,处理虚假唤醒
    while (head == NULL) {
      pthread_cond_wait(&c, &lock);
    }
    struct conn_args *args = get_args();
    pthread_mutex_unlock(&lock);

    if (args) {
      request_serve_static(args->fd, args->filename, args->filesize);
      printf("Request for %s is removed from the buffer\n-----------------\n",
             args->filename);
      close_or_die(args->fd);
      free(args->filename); // 释放复制的字符串内存
      free(args);
    }
  }
}

4. 补充内存释放逻辑

在工作线程中,先释放filename的内存,再释放conn_args结构体,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Tony

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 12:10:56