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

循环调用accept系统调用导致套接字连接损坏问题排查

问题根源与解决方案

核心问题:线程参数传递错误

你当前代码的致命问题在于:主线程把循环内的局部变量newsockfd的地址传给了新创建的线程。newsockfd是栈上的局部变量,主线程调用pthread_create后会立刻回到循环顶部,下一次accept会直接覆盖这个变量的值。如果新线程还没来得及读取newsockfd的内容,就会拿到错误的文件描述符——可能是另一个新连接的fd,甚至是已经失效的fd,这直接导致getpeername调用失败返回-1,后续读取socket数据、解析请求的步骤也全都会出错。

修复方案

解决核心是让每个线程拿到独立且不会被覆盖的文件描述符副本,以下是两种可靠实现方式:

方式1:动态分配内存保存文件描述符(推荐)

主线程为每个新的newsockfd分配一块独立内存,把fd存入后传给线程;线程处理完成后释放这块内存,确保数据不会被主线程覆盖。

修改后的主线程代码:

for(;;)
{
    int newsockfd = accept(sockfd, (struct sockaddr *)&host_addr, (socklen_t *)&host_addrlen);
    if(newsockfd < 0)
    {
        sendf(stderr, LOG_ERROR, "Failed to accept connection...\n");
        continue;
    }
    // 为fd分配独立内存
    int *fd_ptr = malloc(sizeof(int));
    if (!fd_ptr) {
        sendf(stderr, LOG_ERROR, "Failed to allocate memory for fd...\n");
        close(newsockfd);
        continue;
    }
    *fd_ptr = newsockfd;

    pthread_t thread;
    if(pthread_create(&thread, NULL, handler, fd_ptr) != 0)
    {
        sendf(stderr, LOG_ERROR, "Failed to create a new thread...\n");
        free(fd_ptr);
        close(newsockfd);
        return -4;
    }
    // 让线程退出后自动回收资源,避免内存泄漏
    pthread_detach(thread);
}

修改后的handler函数:

void * handler(void * argument)
{
    int *fd_ptr = (int *)argument;
    int sockfd = *fd_ptr;
    // 先释放动态分配的内存
    free(fd_ptr);

    struct sockaddr_in client_addr;
    socklen_t client_addrlen = sizeof(client_addr); // 统一用socklen_t类型,避免类型不匹配

    int sockn = getpeername(sockfd, (struct sockaddr *)&client_addr, &client_addrlen);
    if(sockn < 0)
    {
        sendf(stderr, LOG_ERROR, "Failed to get client's address, got \x1b[35msockn\x1b[0m \x1b[33;3m%d\x1b[0m...\n", sockn);
        close(sockfd); // 必须关闭socket,避免fd泄漏
        pthread_exit(NULL);
    }

    char * input = malloc(sizeof(char) * BUFFER_SIZE);
    if (!input) {
        sendf(stderr, LOG_ERROR, "Failed to allocate input buffer...\n");
        close(sockfd);
        pthread_exit(NULL);
    }

    // 留一个字节给终止符,避免缓冲区溢出
    int valread = read(sockfd, input, BUFFER_SIZE - 1);
    if(valread < 0)
    {
        sendf(stderr, LOG_ERROR, "Failed to read from socket, got \x1b[35mvalread\x1b[0m \x1b[33;3m%d\x1b[0m...\n", valread);
        free(input);
        close(sockfd);
        pthread_exit(NULL);
    }
    input[valread] = '\0'; // 手动添加字符串终止符,确保sscanf正常工作

    char * method = malloc(sizeof(char) * BUFFER_SIZE);
    char * url = malloc(sizeof(char) * BUFFER_SIZE);
    char * version = malloc(sizeof(char) * BUFFER_SIZE);
    // 检查内存分配是否成功
    if (!method || !url || !version) {
        sendf(stderr, LOG_ERROR, "Failed to allocate request buffers...\n");
        free(method); free(url); free(version); free(input);
        close(sockfd);
        pthread_exit(NULL);
    }

    sscanf(input, "%s %s %s", method, url, version);
    sendf(stdout, LOG_DEBUG, "Client \x1b[33;3m%s:%u\x1b[0m has sent a \x1b[33;3m%s\x1b[0m request to \x1b[33;3m%s\x1b[0m!\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), method, url);

    // ... 后续处理逻辑

    // 释放所有分配的内存,关闭socket
    free(method);
    free(url);
    free(version);
    free(input);
    close(sockfd);
    pthread_exit(NULL);
}

方式2:直接传递fd值(移植性较差,仅作参考)

在多数系统中,int和void*的内存长度一致,可以直接把fd强转为void*传递,不需要动态分配内存:

主线程修改:

if(pthread_create(&thread, NULL, handler, (void *)(intptr_t)newsockfd) != 0)

handler函数修改:

int sockfd = (int)(intptr_t)argument;

这种方式更简洁,但仅保证在32位/64位系统中int与void*长度匹配的场景下有效,移植性不如第一种方式。

额外注意事项

  • 所有打开的socket必须在处理完毕后调用close关闭,否则会导致文件描述符泄漏。
  • read读取的原始数据没有字符串终止符,必须手动添加\0,否则sscanf会触发未定义行为。
  • 所有malloc分配的内存必须对应free,避免内存泄漏。
  • 使用pthread_detach让线程退出后自动回收资源,若用pthread_join会阻塞主线程,不适合高并发场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 13:47:46