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
相关产品推荐
相关产品推荐

