《UNIX网络编程 卷1》 笔记: 描述符传递技术
来源:互联网 发布:易娱网络待遇好吗 编辑:程序博客网 时间:2024/06/03 18:13
Linux提供了一种从一个进程向另一个进程传递任意打开的描述符的技术,这两个进程可以无亲缘关系。这种技术要求首先在这两个进程之间创建一个Unix域套接字,然后使用sendmsg跨套接字发送一个特殊的消息,这个消息由内核来处理,会把打开的描述符传递到接收进程。
先来看看要使用的数据结构和函数。
struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */};struct cmsghdr {size_t cmsg_len; /* Data byte count, including header (type is socklen_t in POSIX) */int cmsg_level; /* Originating protocol */int cmsg_type; /* Protocol-specific type *//* followed byunsigned char cmsg_data[]; */};ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);msghdr结构表示数据消息首部,msg_control指向辅助数据,msg_controllen指明了辅助数据的长度(包括辅助数据首部)。
cmsghdr结构体表示辅助数据首部。为了传递描述符,我们将cmsg_level取值为SOL_SOCKET,cmsg_type取值为SCM_RIGHTS,实际的辅助数据长度为4字节(描述符的大小)。为此我们定义了一个表示辅助数据的联合体:
union {struct cmsghdr cm; /*辅助数据首部*/char control[CMSG_SPACE(sizeof(int))]; /*包含4字节数据和辅助数据首部*/} control_un;传递描述符的具体步骤如下:
1. 如果是父子进程之间传递描述符,则父进程调用socketpair函数创建一个流管道。如果是无亲缘关系的进程,则进程之间使用Unix域套接字通信,就像上节我们给出的客户与服务器之间通信的程序一样。
2. 发送进程打开一个描述符,创建一个msghdr结构,其中的辅助数据含有待传递的描述符,然后调用sendmsg函数发送描述符。即使之后进程调用close函数关闭描述符,但是对于接收进程它仍然保持打开的状态。因为发送一个描述符会使该描述符的引用计数加1。
3. 接收进程调用recvmsg函数接收描述符。这个描述符的值并不一定和发送进程发送的描述符的值相同,但是它们都指向内核中相同的文件表项。
发送描述符的函数write_fd实现如下,参数fd是Unix域套接字描述符,参数sendfd是要发送的描述符。
ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd){struct msghdr msg;struct iovec iov[1];/*辅助数据*/union {struct cmsghdr cm;char control[CMSG_SPACE(sizeof(int))];} control_un;struct cmsghdr *cmptr;msg.msg_name = NULL;msg.msg_namelen = 0;iov[0].iov_base = ptr;iov[0].iov_len = nbytes;msg.msg_iov = iov;msg.msg_iovlen = 1;msg.msg_control = control_un.control;msg.msg_controllen = sizeof(control_un.control);cmptr = CMSG_FIRSTHDR(&msg);cmptr->cmsg_len = CMSG_LEN(sizeof(int));cmptr->cmsg_level = SOL_SOCKET;cmptr->cmsg_type = SCM_RIGHTS;*((int *)CMSG_DATA(cmptr)) = sendfd; /*要传递的描述符*/return sendmsg(fd, &msg, 0); /*发送数据*/}ssize_t Write_fd(int fd, void *ptr, size_t nbytes, int sendfd){ssize_t n;if ((n = write_fd(fd, ptr, nbytes, sendfd)) < 0)err_sys("write_fd error");return n;}接收描述的函数read_fd的实现如下:
ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd){struct msghdr msg;struct iovec iov[1];ssize_t n;/*辅助数据*/union {struct cmsghdr cm;char control[CMSG_SPACE(sizeof(int))];} control_un;struct cmsghdr *cmptr;msg.msg_name = NULL;msg.msg_namelen = 0;iov[0].iov_base = ptr;iov[0].iov_len = nbytes;msg.msg_iov = iov;msg.msg_iovlen = 1;msg.msg_control = control_un.control;msg.msg_controllen = sizeof(control_un.control);/*读取数据*/if ((n = recvmsg(fd, &msg, 0)) <= 0)return n;/*解析出辅助数据*/if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {if (cmptr->cmsg_level != SOL_SOCKET)err_quit("control level != SOL_SOCKET");if (cmptr->cmsg_type != SCM_RIGHTS)err_quit("control type != SCM_RIGHTS");*recvfd = *((int *)CMSG_DATA(cmptr)); /*获取描述符*/} else *recvfd = -1;return n;}ssize_t Read_fd(int fd, void *ptr, size_t nbytes, int *recvfd){ssize_t n;if ( (n = read_fd(fd, ptr, nbytes, recvfd)) < 0)err_sys("read_fd error");return n;}
书中给出了一个描述符传递的例子,实现了两个程序mycat和openfile。mycat程序创建一个流管道,调用fork函数创建一个子进程,然后在子进程中调用execl函数执行openfile程序,将流管道一端的描述符(调用execl函数后已经打开的描述符不会关闭)和要打开的文件路径通过execl函数的传给openfile程序。openfile程序打开文件,然后将它的描述符通过流管道传递给父进程。父进程读取描述符,将文件输出到标准输出。
mycat程序的主体功能由my_open函数实现,代码如下:
int my_open(const char *pathname, int mode){int fd, sockfd[2], status;pid_t childpid;char c, argsockfd[10], argmode[10];/*创建一个流管道*/Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);if ((childpid = Fork()) == 0) {Close(sockfd[0]);/*流管道本进程端对应的描述符*/snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);/*文件打开模式*/snprintf(argmode, sizeof(argmode), "%d", mode);/*int execl(const char *path, const char *arg0, ... , (char *)0 ); */execl("./openfile", "openfile", argsockfd, pathname, argmode, (char*)NULL); /*执行openfile程序*/err_sys("execl error");}Close(sockfd[1]);Waitpid(childpid, &status, 0); /*等待子进程终止*/if (WIFEXITED(status) == 0)err_quit("child did not terminate");if ((status = WEXITSTATUS(status)) == 0) /*子进程正常终止*/Read_fd(sockfd[0], &c, 1, &fd); /*读取子进程传递的文件描述符*/else { /*子进程执行出错*/errno = status; fd = -1;}Close(sockfd[0]);return fd;}mycat程序的代码如下:
int main(int argc, char **argv){int fd, n;char buff[BUFFSIZE];if (argc != 2)err_quit("usage: mycat <pathname>");/*fork并execl openfile程序,打开一个文件传回其描述符到本进程*/if ((fd = my_open(argv[1], O_RDONLY)) < 0)err_sys("cannot open %s", argv[1]);while ((n = Read(fd, buff, BUFFSIZE)) > 0)Write(STDOUT_FILENO, buff, n);exit(0);}openfile程序的代码如下:
int main(int argc, char **argv){int fd;if (argc != 4)err_quit("openfile <sockfd#> <filename> <mode>");if ((fd = open(argv[2], atoi(argv[3]))) < 0)exit((errno > 0) ? errno : 255);/*通过流管道发送描述符时,我们总是发送至少1字节数据*/if (write_fd(atoi(argv[1]), "", 1, fd) < 0)exit((errno > 0) ? errno : 255);exit(0);}如注释所示:通过流管道发送描述符(辅助数据)时,我们总是发送至少1字节数据。
阅读全文
0 0
- 《UNIX网络编程 卷1》 笔记: 描述符传递技术
- Unix网络编程(卷1)—笔记
- 《UNIX网络编程 卷1》 笔记: 广播
- 《UNIX网络编程 卷1》 笔记: 线程
- UNIX网络编程卷1 服务器程序设计范式5 预先派生子进程,由父进程向子进程传递套接字描述符
- 《UNIX网络编程 卷1》 笔记: UNIX域协议
- 《UNIX网络编程 卷1》 笔记补充内容: 高级轮询技术epoll
- Unix 网络编程卷1
- 《UNIX网络编程 卷1》 笔记: readn和writen函数
- 《UNIX网络编程 卷1》 笔记: 名字与地址转换
- 《UNIX网络编程 卷1》 笔记: 高级I/O函数
- 《UNIX网络编程 卷1》 笔记: 竞争条件!
- 《UNIX网络编程 卷1》 笔记: 互斥锁与条件变量
- 《UNIX网络编程 卷1》 笔记: 服务器程序设计范式
- 《UNIX网络编程 卷2》 笔记: Posix消息队列(1)
- Unix 网络编程卷一- 学习笔记
- Unix网络编程卷一笔记
- 学习《UNIX网络编程卷一》笔记
- 实现不同用户登录显示不同页面
- 基于 vue2 的响应式基础组件(滚动条)
- http网络请求 返回不同的statusCode(状态码)500,200,400等
- Socket Server-基于线程池的TCP服务器
- [Unity Graphics] 菲涅耳效应(The Fresnel Effect)
- 《UNIX网络编程 卷1》 笔记: 描述符传递技术
- java.lang.Object是如何成为默认父类的
- UISlider相关整理
- 原生javascript实现分享到朋友圈功能 支持ios和android
- [Windows ] windows 下实时打印日志
- navicat ubuntu界面乱码
- USACO-Section1.3 Mixing Milk [贪心算法]
- Java 堆、栈、方法区
- Qt透明窗体