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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 05:35:26