将套接字文件描述符传递给子进程能否正常工作?如何通过exec实现?
问题解答
能不能直接把文件描述符作为命令行参数传给子进程?
可以,但不推荐。
文件描述符本质是整数,你可以把它转成字符串作为exec*系列函数的命令行参数传给子进程,子进程再转回整数使用。默认情况下exec不会关闭打开的文件描述符(除非你给fd设置了FD_CLOEXEC标志),所以子进程拿到的fd数值对应的套接字连接是有效的。
但这种方法存在两个明显问题:
- 安全风险:命令行参数会被
ps等工具暴露,任何能查看进程列表的用户都能获取到fd数值,存在泄露或滥用的可能。 - 可靠性问题:如果父进程在
exec前意外关闭该fd,或者fd被系统复用(概率低但存在),子进程拿到的数值就对应无效连接了。
推荐的文件描述符传递方法
1. 利用fork+exec的fd继承(配合参数传递)
如果坚持用命令行参数方式,记得先取消fd的FD_CLOEXEC标志,确保exec后fd不会被关闭:
// conn_fd 是 accept 得到的文件描述符 int flags = fcntl(conn_fd, F_GETFD); flags &= ~FD_CLOEXEC; fcntl(conn_fd, F_SETFD, flags); // 将 fd 转为字符串作为参数 char fd_str[16]; snprintf(fd_str, sizeof(fd_str), "%d", conn_fd); pid_t pid = fork(); if (pid == 0) { // 子进程执行独立可执行文件 execl("./chess_match", "chess_match", fd_str, NULL); perror("execl failed"); exit(1); } else if (pid > 0) { // 父进程关闭 fd,子进程已继承该 fd 的副本 close(conn_fd); }
子进程只需将参数转回整数,即可直接用该fd读写套接字。
2. 通过UNIX域套接字传递文件描述符(更安全可靠)
这是Linux下进程间传递文件描述符的标准方法,利用sendmsg和recvmsg函数,通过SCM_RIGHTS辅助数据传递fd,能避免命令行暴露的问题,可靠性更高。
父进程步骤:
- 创建UNIX域套接字对(
socketpair); fork后,子进程通过exec加载独立可执行文件,同时将其中一个套接字的fd作为命令行参数传入;- 父进程通过UNIX域套接字,用
sendmsg发送玩家的连接fd; - 发送完成后,父进程关闭连接fd和UNIX域套接字。
示例代码片段:
// 创建UNIX域套接字对 int sock_pair[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) == -1) { perror("socketpair failed"); return -1; } pid_t pid = fork(); if (pid == 0) { close(sock_pair[1]); // 子进程关闭无用端 char sock_fd_str[16]; snprintf(sock_fd_str, sizeof(sock_fd_str), "%d", sock_pair[0]); execl("./chess_match", "chess_match", sock_fd_str, NULL); perror("execl failed"); exit(1); } else if (pid > 0) { close(sock_pair[0]); // 父进程关闭无用端 // 准备发送文件描述符 struct msghdr msg = {0}; struct iovec iov[1]; char buf[1] = {0}; // 必须保留一个iovec,哪怕为空 iov[0].iov_base = buf; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; char cmsg_buf[CMSG_SPACE(sizeof(int))]; msg.msg_control = cmsg_buf; msg.msg_controllen = sizeof(cmsg_buf); struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsg), &conn_fd, sizeof(int)); // 发送fd if (sendmsg(sock_pair[1], &msg, 0) == -1) { perror("sendmsg failed"); close(conn_fd); close(sock_pair[1]); return -1; } close(conn_fd); close(sock_pair[1]); }
子进程步骤:
- 从命令行参数获取UNIX域套接字的fd;
- 用
recvmsg接收父进程发来的连接fd; - 关闭UNIX域套接字,开始用接收的fd与玩家通信。
示例代码片段:
int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <unix_sock_fd>\n", argv[0]); exit(1); } int unix_sock_fd = atoi(argv[1]); struct msghdr msg = {0}; struct iovec iov[1]; char buf[1]; iov[0].iov_base = buf; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; char cmsg_buf[CMSG_SPACE(sizeof(int))]; msg.msg_control = cmsg_buf; msg.msg_controllen = sizeof(cmsg_buf); // 接收fd if (recvmsg(unix_sock_fd, &msg, 0) == -1) { perror("recvmsg failed"); close(unix_sock_fd); exit(1); } struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); int conn_fd = *(int*)CMSG_DATA(cmsg); close(unix_sock_fd); // 现在可以用 conn_fd 与玩家通信 // ... }
这种方法的优势:
- 连接fd不会暴露在命令行,安全性更高;
- 传递的是fd的有效引用,系统会确保子进程拿到的fd有效,父进程发送后可关闭自身的fd副本,不影响子进程使用。
内容的提问来源于stack exchange,提问作者Adam Shkolnik
相关产品推荐
相关产品推荐

