《UNIX网络编程 卷1》 笔记: 描述符传递技术
2017-05-27 14:42
417 查看
Linux提供了一种从一个进程向另一个进程传递任意打开的描述符的技术,这两个进程可以无亲缘关系。这种技术要求首先在这两个进程之间创建一个Unix域套接字,然后使用sendmsg跨套接字发送一个特殊的消息,这个消息由内核来处理,会把打开的描述符传递到接收进程。
先来看看要使用的数据结构和函数。
cmsghdr结构体表示辅助数据首部。为了传递描述符,我们将cmsg_level取值为SOL_SOCKET,cmsg_type取值为SCM_RIGHTS,实际的辅助数据长度为4字节(描述符的大小)。为此我们定义了一个表示辅助数据的联合体:
1. 如果是父子进程之间传递描述符,则父进程调用socketpair函数创建一个流管道。如果是无亲缘关系的进程,则进程之间使用Unix域套接字通信,就像上节我们给出的客户与服务器之间通信的程序一样。
2. 发送进程打开一个描述符,创建一个msghdr结构,其中的辅助数据含有待传递的描述符,然后调用sendmsg函数发送描述符。即使之后进程调用close函数关闭描述符,但是对于接收进程它仍然保持打开的状态。因为发送一个描述符会使该描述符的引用计数加1。
3. 接收进程调用recvmsg函数接收描述符。这个描述符的值并不一定和发送进程发送的描述符的值相同,但是它们都指向内核中相同的文件表项。
发送描述符的函数write_fd实现如下,参数fd是Unix域套接字描述符,参数sendfd是要发送的描述符。
书中给出了一个描述符传递的例子,实现了两个程序mycat和openfile。mycat程序创建一个流管道,调用fork函数创建一个子进程,然后在子进程中调用execl函数执行openfile程序,将流管道一端的描述符(调用execl函数后已经打开的描述符不会关闭)和要打开的文件路径通过execl函数的传给openfile程序。openfile程序打开文件,然后将它的描述符通过流管道传递给父进程。父进程读取描述符,将文件输出到标准输出。
mycat程序的主体功能由my_open函数实现,代码如下:
先来看看要使用的数据结构和函数。
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 by unsigned 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字节数据。
相关文章推荐
- 学习笔记——监控视频事件的描述方法与识别技术
- Java核心技术 卷一 笔记十 参数传递设计的调用
- 《UNIX网络编程 卷1》 笔记补充内容: 高级轮询技术epoll
- 利用NTLDR进入RING0的方法及MGF病毒技术分析笔记
- 数据库开发技术与工程实践 学习笔记
- COM技术内幕学习笔记
- DLL技术笔记(1)
- .NET技术学习笔记:
- DirectShow技术描述与应用(2)
- (转载)AIX PowerPC体系结构及其溢出技术学习笔记
- C#学习笔记一--C#中的参数传递
- DirectShow技术描述与应用
- 学习笔记之 O/R 映射技术的王牌Hibernate框架
- VC++技术内幕(第四版)笔记(第4章)
- 学习笔记之什么是持久化和对象关系映射ORM技术
- COM技术内幕学习笔记(2)
- IssueVision 学习笔记(一)-----使用SoapHeader传递Web Serivices自定义的身份验证数据
- 多进程参数传递技术探讨
- WIN32 API/WTL 学习笔记(消息传递过程)
- VC++技术内幕(第四版)笔记(第3章)