linux下最可靠方式是直接遍历/proc/self/fd目录:用opendir()和readdir()读取,仅保留纯数字名称的条目(如"5"),跳过"stdin"等别名;对每个有效fd调用readlink()获取路径,避免使用lsof或system()等不可靠外部命令。

Linux下通过/proc/self/fd读取文件描述符列表
在Linux上,C++程序无法用标准库直接枚举当前进程的fd,必须依赖/proc/self/fd这个伪目录。它本质是符号链接集合,每个0、1、2等子项都指向对应fd打开的路径或设备。
实操时需注意:opendir("/proc/self/fd")可能因权限或容器环境失败;遍历时要跳过.和..;readlink()返回的路径可能含(deleted)后缀或socket:[12345]这类非文件路径。
- 用
readdir()遍历目录项,对每个d_name调用readlink()获取目标路径 - 若
readlink()返回-1且errno == EINVAL,说明该fd是匿名管道、eventfd、timerfd等无路径对象 - 对socket fd,可进一步用
getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &len)查类型(SOCK_STREAM等) - 避免在多线程中长期持有
DIR*——/proc/self/fd内容随时变化,一次遍历结果仅代表快照
判断fd类型不能只看readlink结果
readlink("/proc/self/fd/5", buf, sizeof(buf)-1)返回pipe:[123456]或socket:[789012]时,仅靠字符串匹配无法区分pipe、fifo、socket或inotify fd。Linux内核不提供统一API返回fd语义类型,必须结合fstat()和ioctl()交叉验证。
- 先
fstat(fd, &st):若st.st_mode & S_IFIFO为真,则是命名管道;若S_ISSOCK(st.st_mode)为真,再进socket分支 - 对socket fd,
getsockname()可查协议族(AF_INET等),getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len)能判断是否已出错 - 对普通文件,
fstat()的st_dev/st_ino可用于去重;但st_size对设备文件或/proc项无意义 - 不要假设
readlink结果可直接open()——socket:[12345]或anon_inode:[eventfd]根本不是文件路径
获取fd状态需分场景调用不同系统调用
“状态”含义模糊:是可读/可写/异常就绪?还是close-on-exec标志?或是socket连接状态?没有单一函数能返回全部。必须按需组合系统调用。
- 查close-on-exec:用
fcntl(fd, F_GETFD),返回值含FD_CLOEXEC位则设定了 - 查文件访问模式(只读/读写等):
fcntl(fd, F_GETFL),与O_RDONLY/O_RDWR等宏比对 - 查socket连接状态:对TCP socket,
getpeername()成功表示已连接,errno == ENOTCONN表示未连接,ENOTSOCK说明不是socket - 实时I/O就绪状态需用
poll()或epoll_ctl(EPOLL_CTL_ADD)后epoll_wait(),不能从/proc推断
跨平台不可行,Windows无等效机制
Windows没有/proc,也没有用户态可安全遍历当前进程所有句柄的公开API。NtQuerySystemInformation(SystemHandleInformation)虽能枚举,但需SeDebugPrivilege权限、结构体随系统版本变化、且返回的是全局句柄表而非当前进程专属视图——实际使用风险高,调试器和杀软常拦截此类调用。
若必须支持Windows,唯一可靠路径是:在每次CreateFile、socket、_open等创建句柄的地方手动登记到自维护的std::map中,并配套CloseHandle钩子。但这要求全程控制所有I/O入口,对第三方库无效。
多数生产场景下,应接受“Linux-only”前提,把精力放在解析/proc/self/fd的健壮性上——比如处理readlink返回路径过长(ERANGE)、stat失败(ENOENT表示fd刚被关闭)、以及容器中/proc挂载点被隐藏等边界情况。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











