Sword 进程间传递打开的文件描述符(demo)
demo
/*进程间传递打开的文件描述符*/ #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include <unistd.h> #include<sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define CONTROLLEN CMSG_LEN(sizeof(int)) /** * @brief 发送目标文件描述符 * @param s 传递信息的 UNIX 域 文件描述符 * @param fd 待发送的文件描述符 */ void send_fd(int s, int fd) { struct iovec iov[1]; struct msghdr msg; /* 设计说明: buf[0]用来标记状态, buf[1]固定值为NULL */ char buf[2]; struct cmsghdr* cmptr = NULL; // 1.参数校验 // 协议设置 msg.msg_name = NULL; msg.msg_namelen = 0; // 缓冲区设置 iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); msg.msg_iov = iov; msg.msg_iovlen = 1; // 设置辅助数据 if (fd < 0) { // 无效文件描述符,不用传递辅助数据 msg.msg_control = NULL; msg.msg_controllen = 0; // 设置状态异常 buf[0] = 1; } else { cmptr = malloc(CONTROLLEN); cmptr->cmsg_len = CONTROLLEN; cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; msg.msg_control = cmptr; msg.msg_controllen = CONTROLLEN; *(int*)CMSG_DATA(cmptr) = fd; // 设置状态正常 buf[0] = 0; } if (sendmsg(s, &msg, 0) != 2) { printf("sendmsg failed .\n"); } if (cmptr) { free(cmptr); cmptr = NULL; } } /** * @brief 接受文件描述符 * @param fd 传递信息的 UNIX 域 文件描述符 */ int recv_fd(int s) { struct iovec iov[1]; struct msghdr msg; char buf[2]; struct cmsghdr * cmptr; ssize_t nr; int sfd = -1; // 参数校验 do { // 协议设置 msg.msg_name = NULL; msg.msg_namelen = 0; // 缓冲区设置 iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); msg.msg_iov = iov; msg.msg_iovlen = 1; // 开辟辅助数据 cmptr = malloc(CONTROLLEN); msg.msg_control = cmptr; msg.msg_controllen = CONTROLLEN; nr = recvmsg(s, &msg, 0); if (nr < 0) { printf("recvmsg failed.\n"); break; } else if (0 == nr) { printf("connection closed by peer.\n"); break; } // 判断状态 if (buf[0]) { // 对方数据出错 printf("data error.\n"); break; } // 提取文件描述符 if (msg.msg_controllen != CONTROLLEN) { // 接收的辅助数据太短,数据异常 printf("no fd.\n"); break; } sfd = *(int*)CMSG_DATA(cmptr); } while (0); if (cmptr) { free(cmptr); cmptr = NULL; } return sfd; } void child_proc(int s) { // 子进程逻辑 int fd = -1; // 1.参数校验 do { // 2.打开一个文件描述符 fd = open("a.txt", O_RDWR, 0666); if (fd < 0) { printf("open file failed.\n"); break; } // 4.向父进程发送文件描述符 send_fd(s, fd); // 睡眠 //sleep(30); } while (0); // 释放资源 if (fd >= 0) { close(fd); } printf("Subprocess exit.\n"); } void father_proc(int s) { // 父进程逻辑 int rfd = -1; char buf[1024] = { 0 }; // 1.参数校验 do { // 2.读取对端文件描述符 rfd = recv_fd(s); if (rfd < 0) { break; } // 3.读取文件内容 read(rfd, buf, 1024); printf("father read text: %s\n", buf); } while (0); if (rfd) { close(rfd); } sleep(100); } void test() { int ret = 0; int fd[2]; pid_t pid; // 1.创建 unix 域通信套接字 ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); if (ret) { // 创建通信失败 printf("socketpair failed.\n"); return; } // 2.创建子进程 pid = fork(); switch (pid) { case -1: // fork失败 printf("fork failed.\n"); close(fd[0]); close(fd[1]); return; case 0: // 子进程 close(fd[0]); child_proc(fd[1]); // 子进程退出 exit(0); break; default: // 父进程 break; } // 父进程关闭fd[1] close(fd[1]); father_proc(fd[0]); } int main() { test(); return 0; }
注意点
关于struct cmsghdr结构体错误用法
void send_fd(int s, int fd) { /* 错误说明: 为啥这段代码执行sendmsg会报错"Bad file descriptor"? 问题的原因是因为将struct cmsghdr定义成栈变量,当struct cmsghdr是栈变量的时候,其长度是固定值16个字节, 可以看出struct cmsghdr栈变量没有为数据部分预留空间,如果不用辅助数据其实也没有问题,如果用了辅助数据就有问题了, 因为struct cmsghdr数据部分没有内存空间,通过观察发现实际上struct cmsghdr的数据部分被存放到msg_iov中了, 因此只要你修改buf的值就会破坏struct cmsghdr数据部分,从而导致被传递的文件描述符是无效的。 */ struct iovec iov[1]; struct msghdr msg; char buf[2]; // 错误点① struct cmsghdr cm; ssize_t nw; iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; cm.cmsg_len = CONTROLLEN; cm.cmsg_level = SOL_SOCKET; cm.cmsg_type = SCM_RIGHTS; *(int*)CMSG_DATA(&cm) = fd; msg.msg_control = &cm; msg.msg_controllen = CONTROLLEN; // 错误点② buf[0] = 1; buf[1] = 3; nw = sendmsg(s, &msg, 0); printf("sendmsg return %ld, error message %s\n", nw, strerror(errno)); }
关于父子进程之间通信说明
在技术上,发送进程实际上向接收进程传送一个指向一打开文件表项的指针。该指针被分配存放在接收进程的第一个可用描述符项中。
(注意,不要造成错觉,以为发送进程和接收进程中的描述符编号是相同的,通常它们是不同的。)两个进程共享同一打开文件表项,
在这一点上与fork之后,父、子进程共享打开文件表项的情况完全相同.
当发送进程将描述符传送给接收进程后,通常它关闭该描述符。发送进程关闭该描述符并不造成关闭该文件或设备,
其原因是该描述符对应的文件仍被视为由接收者进程打开(即使接收进程尚未接收到该描述符)。