循环调用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
相关产品推荐
相关产品推荐

