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

C语言使用poll监听套接字时出现Bad file descriptor错误排查

问题分析与修复:poll监听套接字后的文件描述符错误

问题现象

  • 使用poll监听套接字接收客户端消息,成功接收一条消息后,连接关闭并报错:problem accepting fd: Bad file descriptor.
  • 无论是否将accept()返回的文件描述符加入poll监听列表,该错误都会出现
  • 核心疑惑:若accept从未成功,为何能收到消息?

核心错误分析

1. poll_server函数逻辑完全错误

这是导致报错的根本原因:

  • 每次循环遍历所有fd时重复调用poll,而非调用一次poll后处理所有就绪的fd
  • 只要poll返回>0,就直接关闭监听套接字的fd!第一次accept成功后,监听fd被提前关闭,后续循环再调用accept时使用的是已失效的fd,自然触发"Bad file descriptor"错误
  • 你能收到消息是因为第一次accept是成功的,handle_fd正确读取了客户端消息,但之后监听fd被错误关闭,后续accept调用才会失败

2. handle_fd错误修改polls->count

handle_fd中直接执行polls->count--;,但该客户端fd并未加入poll列表(你注释了poll_add_fd),导致poll的计数混乱,进一步引发逻辑错误

3. 非阻塞accept的错误处理

监听套接字被设置为非阻塞模式,accept可能返回EAGAIN/EWOULDBLOCK(表示当前无新连接),此时不应直接报错,而应忽略该情况

修复后的关键代码

修复poll_server函数

void poll_server(struct polls_st * polls) {
    while (1) {
        // 单次调用poll,等待就绪的文件描述符
        int pollout = poll(polls->poll_fds, polls->count, 100);
        if (pollout == -1) {
            error("poll failed");
            continue;
        }
        if (pollout == 0) {
            // 超时,进入下一轮循环
            continue;
        }

        // 遍历所有fd,处理就绪事件
        for (int i = 0; i < polls->count; i++) {
            if (!(polls->poll_fds[i].revents & POLLIN)) {
                continue;
            }

            // 区分监听fd和客户端fd(假设第一个fd是监听套接字)
            if (i == 0) {
                server_accept(polls, i);
            } else {
                handle_fd(polls, i);
            }
        }
    }
}

修复server_accept函数(处理非阻塞accept)

void server_accept(struct polls_st * polls, int pidx) {
    printf("server accepting fd\n");
    int listener_fd = polls->poll_fds[pidx].fd;
    int new_fd = accept(listener_fd, NULL, NULL);
    if (new_fd == -1) {
        // 非阻塞模式下无新连接,属于正常情况
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            error("problem accepting fd");
        }
        return;
    }

    // 将客户端fd设置为非阻塞
    int en = 1;
    if (ioctl(new_fd, FIONBIO, (char *) &en) == -1) {
        error("problem setting client fd non-blocking");
        close(new_fd);
        return;
    }

    // 将客户端fd加入poll监听列表
    poll_add_fd(polls, new_fd);
}

修复handle_fd函数(正确管理poll列表)

void handle_fd(struct polls_st * polls, int idx) {
    int fd = polls->poll_fds[idx].fd;
    char buf[32] = {0,};
    int recv_len = sizeof(buf) - 1; // 预留字节存储字符串结束符
    int nread = recv(fd, buf, recv_len, 0);

    if (nread == -1) {
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            error("problem recv from client");
        }
        return;
    }

    if (nread == 0) {
        // 客户端主动关闭连接
        printf("Client closed connection\n");
        close(fd);
        // 从poll列表中移除该fd:用最后一个元素覆盖当前位置,计数减一
        polls->poll_fds[idx] = polls->poll_fds[polls->count - 1];
        polls->count--;
        return;
    }

    printf("Recieved from client: %s\n", buf);
    // 可在此添加客户端消息的处理逻辑(如HTTP响应)
}

修复start_server中的snprintf溢出问题

将:

snprintf(port_str, 6, "%d", port_num);

修改为:

snprintf(port_str, sizeof(port_str), "%d", port_num);

修复说明

  • 保留监听套接字的有效性,不再错误关闭,避免后续accept调用使用无效fd
  • 正确使用poll:单次调用后处理所有就绪事件,而非循环重复调用
  • 非阻塞模式下的系统调用增加错误区分,避免误报正常情况
  • 正确管理poll列表中的fd,保证计数和列表元素一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:47:41