LINUX进程间传递描述符_第1页
LINUX进程间传递描述符_第2页
LINUX进程间传递描述符_第3页
LINUX进程间传递描述符_第4页
LINUX进程间传递描述符_第5页
已阅读5页,还剩4页未读 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、进程间传递描述符每个进程都拥有自己独立的进程空间,这使得描述符在进程之间的传递变得有点复杂,这个属于高级进程间通信的内容,下面就来说说。顺便把Linux 和Windows平台都讲讲。Linux下的描述符传递Linux系统系下,子进程会自动继承父进程已打开的描述符,实际应用中,可能父进程需要向子进程传递 后打开的描述符”,或者子进程需要向父进程传递;或者两个进程可能是无关的,显然这需要一套传递机制。简单的说,首先需要在这两个进程之间建立一个Unix域套接字接口作为消息传递的通道( Linux 系统上使用socketpair函数可以很方面便的建立起传递通道),然后发送进程调用sendmsg向通道发

2、送一个特殊的消息,内核将对这个消息做特殊处理,从而将打开的描述符传递到接收进程。然后接收方调用recvmsg从通道接收消息,从而得到打开的描述符。然而实际操作起来并不像看起来那 样单纯。先来看几个注意点:1需要注意的是传递描述符并不是传递一个int型的描述符编号,而是在接收进程中创建一个新的描述符,并且在内核的文件表中,它与发送进程发送的描述符指向相同的项。2在进程之间可以传递任意类型的描述符,比如可以是pipe , open , mkfifo 或socket , accept 等函数返回的描述符,而不限于套接字。3 一个描述符在传递过程中 (从调用sendmsg发送到调用recvmsg接收)

3、,内核会将其标记为 在飞行中气in flight )。在这段时间内,即使发送方试图关闭该描述符,内核仍会为接收进程保持打开状态。发送描述符会 使其引用计数加1。4描述符是通过辅助数据发送的(结构体msghdr的msg_control成员),在发送和接收描述符时,总是发送 至少1个字节的数据,即使这个数据没有任何实际意义。 否则当接收返回0时,接收方将不能区分这意味着 没 有数据”(但辅助数据可能有套接字)还是 文件结束符”。5具体实现时,msghdr的msg_control 缓冲区必须与cmghdr结构对齐,可以看到后面代码的实现使用了一 个union 结构来保证这一点。msghdr 和 cm

4、sghdr 结构体上面说过,描述符是通过结构体 msghdr的msg_control成员送的,因此在继续向下进行之前,有必要了解一下msghdr 和cmsghdr 结构体,先来看看 msghdr 。struct msghdr void *msg_name;socklen_t msg_namelen;struct iovec *msg_iov; size_t msg_iovlen; void *msg_control;size_t msg_controllen;intmsg_flags;结构成员可以分为下面的四组,这样看起来就清晰多了:1 套接口地址成员 msg_name 与 msg_namel

5、en ;只有当通道是数据报套接口时才需要;msg_name指向要发送或是接收信息的套接口地址。msg_namelen指明了这个套接口地址的长度。msg_name在调用recvmsg 时指向接收地址, 在调用sendmsg时指向目的地址。 注意,msg_name定义为一个 (void *)数据类型,因此并不需要将套接口地址显示转换为(struct sockaddr *)。2 I/O 向量引用 msg_iov 与 msg_iovlen它是实际的数据缓冲区,从下面的代码能看到,我们的1个字节就交给了它;这个msg_iovlen 是msg_iov的个数,不是什么长度。msg_iov成员指向一个 str

6、uct iovec 数组,iovc 结构体在sys/uio.h头文件定义,它没有什么特别的struct iovec ptr_t iov_base; /* Starting address */size_t iov_len; /* Length in bytes */; _ _有了 iovec,就可以使用readv和writev函数在一次函数调用中读取或是写入多个缓冲区,显然比多次read , write 更有效率。readv 和writev的函数原型如下:#include int readv(int fd, const struct iovec *vector, int count);int

7、writev(int fd, const struct iovec *vector, int count);3附属数据缓冲区成员msg_control 与msg_controllen,描述符就是通过它发送的,后面将会看到,msg_control指向附属数据缓冲区,而msg_controllen指明了缓冲区大小。4接收信息标记位msg_flags ;忽略轮到cmsghdr结构了,附属信息可以包括若干个单独的附属数据对象。在每一个对象之前都有一个struct cmsghdr结构。头部之后是填充字节,然后是对象本身。最后,附属数据对象之后,下一个cmsghdr之前也许要有更多的填充字节。struct

8、 cmsghdr socklen_t cmsg_len;int cmsg_level;int cmsg_type;/* u_char cmsg_data; */;cmsg_len附属数据的字节数,这包含结构头的尺寸,这个值是由CMSG_LEN()宏计算的;cmsg_level 表明了原始的协议级别(例如,SOL_SOCKET);cmsg_type 表明了控制信息类型(例如,SCM_RIGHTS,附属数据对象是文件描述符;SCM_CREDENTIALS附属数据对象是一个包含证书信息的结构);被注释的cmsg_data用来指明实际的附属数据的位置,帮助理解。对于 cmsgevel 和 cmsg_t

9、ype ,当下我们只关心 SOL_SOCKET和 SCM_RIGHTS。协议cmsg levelcmsg type说明TCPSOL_SOCKETSCM_RIGHTS SCM CREDS发送/接必描述符,转交控制权 发送/接收用户凭证IPv4IPPROTO_IPIP_RECVDSTADDR IP RECVIF随UDP数据报接收宿地址 随UPD数据报接收接口索引IPv6IPPROTO_IPV6IPV6-DSTOPTS IPV6-HOPLIMIT IPV6-HOPOPTS IPV6-NEXTHOP IPV6-PKTINFO IPV6-RTHDR IPV6-TCLASS指定/接收目的地址选项 指定/接

10、收跳限指定/接收步跳选项 指定下一跳地址指定/接收分组信息 指定/接收接收路由头部 指定/接收分组流通类别msghdr 和 cmsghdr 辅助宏这些结构还是挺复杂的, Linux 系统提供了一系列的宏来简化我们的工作, 这些宏可以在不同的 UNIX 平台之 间进行移植。这些宏是由 cmsg(3) 的 man 手册页描述的,先来认识一下:#include struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg)

11、;size_t CMSG_ALIGN(size_t length);size_t CMSG_SPACE(size_t length);size_t CMSG_LEN(size_t length);void *CMSG_DATA(struct cmsghdr *cmsg);CMSG_LEN()宏输入参数:附属数据缓冲区中的对象大小;计算 cmsghdr 头结构加上附属数据大小, 包括必要的对其字段, 这个值用来设置 cmsghdr 对象的 cmsg_len 成 员。CMSG_SPACE(宏输入参数:附属数据缓冲区中的对象大小;计算 cmsghdr 头结构加上附属数据大小, 并包括对其字段和可能的

12、结尾填充字符, 注意 CMSG_LEN() 值并不包 括可能的结尾填充字符。CMSG_SPACE()宏对于确定所需的缓冲区尺寸是十分有用的。注意如果在缓冲区中有多个附属数据,一定要同时添加多个CMSG_SPACE()宏调用来得到所需的总空间。下面的例子反映了二者的区别:printf(CMSG_SPACE(sizeof(short)=%d/n, CMSG_SPACE(sizeof(short); /返回16printf(CMSG_LEN(sizeof(short)=%d/n, CMSG_LEN(sizeof(short); /返回14CMSG_DATA(宏输入参数:指向 cmsghdr 结构的指

13、针 ;返回跟随在头部以及填充字节之后的附属数据的第一个字节 ( 如果存在 ) 的地址,比如传递描述符时,代码 将是如下的形式:struct cmsgptr *cmptr;int fd = *(int *)CMSG_DATA(cmptr); /发送: *(int *)CMSG_DATA(cmptr) = fd;CMSG_FIRSTHDR(宏输入参数:指向 struct msghdr 结构的指针;返回指向附属数据缓冲区内的第一个附属对象的struct cmsghdr指针。如果不存在附属数据对象则返回的指针值为 NULL 。CMSG_NXTHDR宏输入参数:指向 struct msghdr 结构的指

14、针,指向当前 struct cmsghdr 的指针; 这个用于返回下一个附属数据对象的 struct cmsghdr 指针,如果没有下一个附属数据对象,这个宏就会返回 NULL 。通过这两个宏可以很容易遍历所有的附属数据,像下面的形式:struct msghdr msgh;struct cmsghdr *cmsg;for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;cmsg = CMSG_NXTHDR(&msgh,cmsg) / 得到了 cmmsg就能通过CMSG_DATA宏取得辅助数据了函数 sendmsg 和 recvmsg函数原型如下:#incl

15、ude #include int sendmsg(int s, const struct msghdr *msg, unsigned int flags);int recvmsg(int s, struct msghdr *msg, unsigned int flags);二者的参数说明如下:s,套接字通道,对于 sendmsg是发送套接字,对于recvmsg则对应于接收套接字; msg,信息头结构指针;flags ,可选的标记位,这与send或是sendto 函数调用的标记相同。函数的返回值为实际发送 /接收的字节数。否则返回-1表明发生了错误。具体参考APUE的高级I/O 部分,介绍的很详

16、细。好了准备工作已经做完了,下面就准备进入正题。发送描述符经过了前面的准备工作,是时候发送描述符了,先来看看函数原型:int write_fd(int fd, void *ptr, int nbytes, int sendfd);参数说明如下:fd :发送TCP套接字接口;这个可以是使用socketpair返回的发送套接字接口ptr :发送数据的缓冲区指针;nbytes :发送的字节数;sendfd :向接收进程发送的描述符;函数返回值为写入的字节数,cmsgen = CMSG_LEN(sizeof(int);/ fd 类型是 int,设置长度cmptr-cmsgevel = SOL_SOCK

17、ET;cmptr-cmsg_type = SCM_RIGHTS; / 指明发送的是描述符*(int*)CMSG_DATA(cmptr) = sendfd;/ 把fd 写入辅助数据中#elsemsg.msg_accrights = (caddr_t )&sendfd;/ 这个旧的更方便啊msg.msg_accrightslen = sizeof(int);#endif/ UDP 才需要,无视msg.msg_name = NULL;msg.msg_namelen = 0;/别忘了设置数据缓冲区,实际上1个字节就够了iov0.iov_base = ptr;iov0.iov_len = nbytes;

18、msg.msg_iov = iov;msg.msg_iovlen = 1;return sendmsg(fd, &msg, 0);接收描述符发送方准备好之后,接收方准备接收,函数原型为:int read_fd(int fd, void *ptr, int nbytes, int *recvfd);参数说明如下:fd :接收TCP套接字接口;这个可以是使用socketpair返回的接收套接字接口ptr :接收数据的缓冲区指针;nbytes :接收缓冲区大小;recvfd :用来接收发送进程发送来的描述符;函数返回值为读取的字节数,0说明读取失败;接收函数代码如下,相比发送要复杂一些。int re

19、ad_fd(int fd, void *ptr, int nbytes, int *recvfd)-struct msghdr msg;struct iovec iov1;int n;int newfd;#ifdef HAVE_MSGHDR_MSG_CONTROLunion / 对齐struct cmsghdr cm;char control CMSG_SPACE(sizeof(int);control_un;struct cmsghdr *cmptr;/设置辅助数据缓冲区和长度msg.msg_control = control_un.control;msg.msg_controllen =

20、sizeof(control_un.control);#elsemsg.msg_accrights = (caddr_t) &newfd;/ 这个简单msg.msg_accrightslen = sizeof(int);#endif/ TCP 无视msg.msg_name = NULL;msg.msg_namelen = 0;/设置数据缓冲区iov0.iov_base = ptr;iov0.iov_len = nbytes; msg.msg_iov = iov;msg.msg_iovlen = 1;/设置结束,准备接收if(n = recvmsg(fd, &msg, 0) cmsgen = C

21、MSG_LEN(sizeof(int) /还是必要的检查if(cmptr-cmsgevel != SOL_SOCKET)printf(control level != SOL_SOCKET/n);exit(-1);if(cmptr-cmsg_type != SCM_RIGHTS)printf(control type != SCM_RIGHTS/n);exit(-1);/好了,描述符在这*recvfd = /*fd send.c*/* bailing, 2009/08/14* passing decriptorusing unix domain socket + sendmsg/recvmsg

22、(int*)CMSG_DATA(cmptr);elseif(cmptr = NULL) printf(null cmptr, fd not passed./n);else printf(message len%d if incorrect./n, cmptr-cmsg_len);*recvfd = -1;/ descriptor was not passed#elseif(msg.msg_accrightslen = sizeof(int) *recvfd = newfd;else *recvfd = -1;#endifreturn n;实例:UNIX域Socket的使用:fd_recv.c,

23、 fd_send.c。实现进程间传递文件描述符。?fd_send.c将生成一个fd,并通过UNIX域协议发送。 ?fd_recv.c 将接收fd_send.c生成的fd,并往里面输入*/ #include #include #include #include #include #include #include #include #include static struct sockaddr_un addr = 0;static int create_usockfd(char* path) int sockfd;addr.sun_family = AF_UNIX; strcpy(addr.su

24、n_path, path);sockfd = socket(PF_UNIX, SOCK_DGRAM, 0); if(sockfd = -1)printf(create unix sockfd failed, %d, %sn, errno, strerror(errno); return 0; else printf(create unix sockfd okn);return sockfd;static int send_fd(int sockfd, int fd)struct msghdr msg = 0; char ccmsgCMSG_SPACE(sizeof(fd);struct cms

25、ghdr* cmsg;int rv;msg.msg_name = (struct sockaddr*)&addr; msg.msg_namelen = sizeof(addr);msg.msg_control = ccmsg; msg.msg_controllen = sizeof(ccmsg);cmsg = CMSG_FIRSTHDR(&msg); cmsg-cmsg_level =SOL_SOCKE; Tcmsg-cmsg_type =SCM_RIGHTS;cmsg-cmsg_len = CMSG_LENsi(zeof(fd);*(int*)CMSG_DATA(cmsg) = fd; ms

26、g.msg_control = ccmsg; msg.msg_controllen = cmsg-cmsg_len; msg.msg_flags = 0;printf(sendmsg to %dn, sockfd);rv = (sendmsg(sockfd, &msg, 0) != -1);if(Mprintf(sendmsg ok, fd is %dn, fd);close(fd);elseprintf(sendmsg failed, %d, %sn, errno, strerror(errno);return rv;int main()static char/* bailing, 2009

27、/08/14* passing decriptor* using unix domain socket + sendmsg/recvmsg*/#include #include #include #include #include #include #include static int create_usockfd(char* path) -struct sockaddr_un addr = 0;int opt = 1;int fd;addr.sun_family = AF_UNIX;unlink(path);strcpy(addr.sun_path, path);fd = socket(PF_UNIX, SOCK_DGRAM, 0);if(bind(fd, (struct sockaddr*)&addr, sizeof(addr) path = /tmp/s

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论