FreeRTOS环境下TCP Server的accept()返回-1,errno=9问题排查
解决TCP Server中accept()返回-1(errno=9)的问题
核心问题:监听套接字被提前关闭
在tcp_create_server函数中,创建完tcp_listening_task任务后,代码会执行到CLEAN_UP分支调用close(listen_sock);。此时任务可能还未启动运行,监听套接字就已被关闭,导致任务里的accept使用已失效的文件描述符,直接触发errno=9(EBADF,无效文件描述符)。
修复方法:
仅在绑定、监听失败时执行close(listen_sock),成功创建任务后跳过清理逻辑:
void TCP::tcp_create_server(sa_family_t serv_family, in_addr_t serv_addr, in_port_t serv_port) { // ... 原代码省略 ... ESP_LOGI(TAG, "Socket listening . . ."); xTaskCreate(tcp_listening_task, "tcp_listening_task", 4096, nullptr, 5, nullptr); // 新增:任务创建成功后直接返回,不执行close(listen_sock) return; CLEAN_UP: close(listen_sock); }
其他潜在问题修复(避免后续踩坑)
1. 全局静态sock变量导致多连接冲突
当前sock是类静态成员,多客户端连接时,新的sock会覆盖旧值,导致之前的接收/发送任务使用错误套接字。
修复方法:
将sock作为参数传递给接收任务:
- 修改
tcp_listening_task中的任务创建代码:
// 原代码 xTaskCreate(tcp_task_recv, "tcp_task_recv", 4096, nullptr, 5, nullptr); // 修改为 xTaskCreate(tcp_task_recv, "tcp_task_recv", 4096, (void*)&sock, 5, nullptr);
- 修改
tcp_task_recv函数,从参数获取当前连接的套接字:
void TCP::tcp_task_recv(void *arg) { int client_sock = *(int*)arg; int n; int number_receiving = 0; struct timeval tv; tv.tv_sec = 1; setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO,(struct timeval *)&tv,sizeof(struct timeval)); ESP_LOGI(TAG,"server waiting message on socket %d", client_sock); for (;;) { n = recv(client_sock, (char *)buf, sizeof(buf), 0); if (n < 0) { ESP_LOGE(TAG, "Error receiving on socket %d: errno %d", client_sock, errno); number_receiving++; if (number_receiving == 10) { close(client_sock); vTaskDelete(nullptr); break; } } else if (n == 0) { ESP_LOGW(TAG, "Connection closed on socket %d", client_sock); close(client_sock); vTaskDelete(nullptr); break; } else { if (clbk_tcp_recv != nullptr) clbk_tcp_recv(buf, n, (struct sockaddr *)&source_addr); number_receiving = 0; } } }
2. tcp_sendd重复创建队列和任务
每次调用tcp_sendd都会创建新队列和发送任务,导致资源泄漏,且多任务会同时操作同一个sock。
修复方法:
在tcp_create_server中初始化队列和发送任务(仅执行一次):
void TCP::tcp_create_server(sa_family_t serv_family, in_addr_t serv_addr, in_port_t serv_port) { // ... 原代码省略 ... ESP_LOGI(TAG, "Socket listening . . ."); // 初始化发送队列和任务 if (xQueue1 == nullptr) { xQueue1 = xQueueCreate(10, sizeof(message)); xTaskCreate(tcp_task_send, "tcp_task_send", 4096, nullptr, 5, nullptr); } xTaskCreate(tcp_listening_task, "tcp_listening_task", 4096, nullptr, 5, nullptr); return; CLEAN_UP: close(listen_sock); }
同时修改tcp_sendd去掉重复创建逻辑:
void TCP::tcp_sendd(void *data, uint32_t length) { message msg; if(sock < 0){ ESP_LOGE(TAG, "No valid client socket"); return; } if(xQueue1 == NULL){ ESP_LOGE(TAG, "Send queue not initialized"); return; } msg.dataPtr = data; msg.len = length; xQueueSend(xQueue1, (void *)&msg, portMAX_DELAY); }
3. IPv6地址转换错误
tcp_listening_task中IPv6地址转换时错误使用sockaddr_in的sin_addr,应改为sockaddr_in6的sin6_addr:
// 原代码 else if (source_addr.ss_family == PF_INET6) { inet6_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1); } // 修改为 else if (source_addr.ss_family == PF_INET6) { inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1); }
内容的提问来源于stack exchange,提问作者WUMBO
相关产品推荐
相关产品推荐

