2022年2022年进程间传递描述符 .pdf

上传人:Che****ry 文档编号:34877126 上传时间:2022-08-19 格式:PDF 页数:11 大小:140.33KB
返回 下载 相关 举报
2022年2022年进程间传递描述符 .pdf_第1页
第1页 / 共11页
2022年2022年进程间传递描述符 .pdf_第2页
第2页 / 共11页
点击查看更多>>
资源描述

《2022年2022年进程间传递描述符 .pdf》由会员分享,可在线阅读,更多相关《2022年2022年进程间传递描述符 .pdf(11页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

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

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

3、在传递过程中(从调用 sendmsg 发送到调用 recvmsg 接收) ,内核会将其标记为“在飞行中”(in flight )。在这段时间内,即使发送方试图关闭该描述符,内核仍会为接收进程保持打开状态。发送描述符会使其引用计数加 1 。4 描述符是通过辅助数据发送的(结构体 msghdr 的 msg_control 成员),在发送和接收描述符时,总是发送至少 1 个字节的数据,即使这个数据没有任何实际意义。否则当接收返回 0 时,接收方将不能区分这意味着“没有数据”(但辅助数据可能有套接字)还是“文件结束符”。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - -

4、- - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 11 页 - - - - - - - - - 5 具体实现时, msghdr 的 msg_control 缓冲区必须与 cmghdr 结构对齐,可以看到后面代码的实现使用了一个union 结构来保证这一点。msghdr 和 cmsghdr 结构体上面说过,描述符是通过结构体 msghdr 的 msg_control 成员送的,因此在继续向下进行之前,有必要了解一下msghdr 和 cmsghdr 结构体,先来看看 msghdr 。cppview plaincopy1.struct msghdr 2.voi

5、d *msg_name; 3. socklen_t msg_namelen; 4.struct iovec *msg_iov; 5.size_t msg_iovlen; 6.void *msg_control; 7.size_t msg_controllen; 8.int msg_flags; 9.; 结构成员可以分为下面的四组,这样看起来就清晰多了:1 套接口地址成员 msg_name 与 msg_namelen ;只有当通道是数据报套接口时才需要; msg_name 指向要发送或是接收信息的套接口地址。 msg_namelen 指明了这个套接口地址的长度。msg_name 在调用 recv

6、msg 时指向接收地址,在调用 sendmsg 时指向目的地址。注意, msg_name 定义为一个 (void *) 数据类型,因此并不需要将套接口地址显示转换为 (struct sockaddr *) 。2 I/O 向量引用 msg_iov 与 msg_iovlen 它是实际的数据缓冲区,从下面的代码能看到,我们的 1 个字节就交给了它;这个 msg_iovlen 是 msg_iov 的个数,不是什么长度。msg_iov 成员指向一个 struct iovec 数组, iovc 结构体在 sys/uio.h 头文件定义,它没有什么特别的。cppview plaincopy名师资料总结 -

7、- -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 11 页 - - - - - - - - - 1.struct iovec 2. ptr_t iov_base; /* Starting address */3.size_t iov_len; /* Length in bytes */4.; 有了 iovec , 就可以使用 readv 和 writev 函数在一次函数调用中读取或是写入多个缓冲区,显然比多次 read ,write 更有效率。 readv 和 writev 的函数原型如下:

8、cppview plaincopy1.#include 2.int readv(int fd, conststruct iovec *vector, int count); 3.int writev(int fd, conststruct iovec *vector, int count); 3 附属数据缓冲区成员 msg_control 与 msg_controllen ,描述符就是通过它发送的,后面将会看到, msg_control 指向附属数据缓冲区, 而 msg_controllen 指明了缓冲区大小。4 接收信息标记位 msg_flags ;忽略轮到 cmsghdr 结构了,附属信息

9、可以包括若干个单独的附属数据对象。在每一个对象之前都有一个 struct cmsghdr 结构。头部之后是填充字节,然后是对象本身。最后,附属数据对象之后,下一个 cmsghdr 之前也许要有更多的填充字节。cppview plaincopy1.struct cmsghdr 2. socklen_t cmsg_len; 3.int cmsg_level; 4.int cmsg_type; 5./* u_char cmsg_data; */6.; cmsg_len 附属数据的字节数,这包含结构头的尺寸,这个值是由 CMSG_LEN() 宏计算的;cmsg_level 表明了原始的协议级别 ( 例

10、如, SOL_SOCKET) ;名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 11 页 - - - - - - - - - cmsg_type 表明了控制信息类型 ( 例如, SCM_RIGHTS ,附属数据对象是文件描述符; SCM_CREDENTIALS , 附属数据对象是一个包含证书信息的结构 ) ;被注释的 cmsg_data 用来指明实际的附属数据的位置,帮助理解。对于 cmsg_level 和 cmsg_type ,当下我们只关心 SOL_SOCKET 和

11、SCM_RIGHTS 。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); size_t CMSG_ALIGN(size_t length); size_t

12、 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 头结构加上附属数据大小,并包括对其字段和可能的结尾填充字符,注意 CMSG_LEN() 值并不包括可能的结尾填充字符。 C

13、MSG_SPACE() 宏对于确定所需的缓冲区尺寸是十分有用的。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 11 页 - - - - - - - - - 注意如果在缓冲区中有多个附属数据,一定要同时添加多个 CMSG_SPACE() 宏调用来得到所需的总空间。下面的例子反映了二者的区别:cppview plaincopy1.printf(CMSG_SPACE(sizeof(short)=%d/n, CMSG_SPACE(sizeof( short); / 返回162.

14、printf(CMSG_LEN(sizeof(short)=%d/n, CMSG_LEN( sizeof( short); / 返回 14CMSG_DATA() 宏输入参数:指向 cmsghdr 结构的指针 ; 返回跟随在头部以及填充字节之后的附属数据的第一个字节 ( 如果存在 ) 的地址,比如传递描述符时,代码将是如下的形式:cppview plaincopy1.struct cmsgptr *cmptr; 2. . . 3.int fd = *(int *)CMSG_DATA(cmptr); / 发送: *(int *)CMSG_DATA(cmptr) = fd;CMSG_FIRSTHDR

15、() 宏输入参数:指向 struct msghdr 结构的指针;返回指向附属数据缓冲区内的第一个附属对象的 struct cmsghdr 指针。如果不存在附属数据对象则返回的指针值为 NULL 。CMSG_NXTHDR() 宏输入参数:指向 struct msghdr 结构的指针,指向当前 struct cmsghdr 的指针;这个用于返回下一个附属数据对象的 struct cmsghdr 指针,如果没有下一个附属数据对象,这个宏就会返回NULL 。通过这两个宏可以很容易遍历所有的附属数据,像下面的形式:cppview plaincopy1.struct msghdr msgh; 名师资料总结

16、 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 11 页 - - - - - - - - - 2.struct cmsghdr *cmsg; 3.for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; 4. cmsg = CMSG_NXTHDR(&msgh,cmsg) 5./ 得到了 cmmsg,就能通过CMSG_DATA()宏取得辅助数据了函数 sendmsg 和 recvmsg 函数原型如下:cppview plaincopy1.#incl

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

18、APUE 的高级 I/O 部分,介绍的很详细。好了准备工作已经做完了,下面就准备进入正题。原文链接: http:/ - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 11 页 - - - - - - - - - int write_fd(int fd, void *ptr, int nbytes, int sendfd);参数说明如下:fd :发送 TCP 套接字接口;这个可以是使用socketpair返回的发送套接字接口ptr :发送数据的缓冲区指针;nbytes :发送的字节数;se

19、ndfd :向接收进程发送的描述符;函数返回值为写入的字节数, cmsg_len = CMSG_LEN(sizeof( int ); / fd类型是 int ,设置长度19. cmptr-cmsg_level = SOL_SOCKET; 20. cmptr-cmsg_type = SCM_RIGHTS; / 指明发送的是描述符21. *(int *)CMSG_DATA(cmptr) = sendfd; / 把 fd 写入辅助数据中名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页

20、,共 11 页 - - - - - - - - - 22.#else23. msg.msg_accrights = (caddr_t)&sendfd; / 这个旧的更方便啊24. msg.msg_accrightslen = sizeof( int ); 25.#endif26./ UDP才需要,无视27. msg.msg_name = NULL; 28. msg.msg_namelen = 0; 29./ 别忘了设置数据缓冲区,实际上1 个字节就够了30. iov0.iov_base = ptr; 31. iov0.iov_len = nbytes; 32. msg.msg_iov = io

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

22、ppview plaincopy1.int read_fd(int fd, void *ptr, int nbytes, int *recvfd) 2. 3.struct msghdr msg; 4.struct iovec iov1; 5.int n; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 11 页 - - - - - - - - - 6.int newfd; 7.#ifdef HAVE_MSGHDR_MSG_CONTROL8.union / 对齐9.stru

23、ct cmsghdr cm; 10.char controlCMSG_SPACE(sizeof( int ); 11. control_un; 12.struct cmsghdr *cmptr; 13./ 设置辅助数据缓冲区和长度14. msg.msg_control = control_un.control; 15. msg.msg_controllen = sizeof(control_un.control); 16.#else17. msg.msg_accrights = (caddr_t) &newfd; / 这个简单18. msg.msg_accrightslen = sizeof(

24、 int ); 19.#endif 20.21./ TCP无视22. msg.msg_name = NULL; 23. msg.msg_namelen = 0; 24./ 设置数据缓冲区25. iov0.iov_base = ptr; 26. iov0.iov_len = nbytes; 27. msg.msg_iov = iov; 28. msg.msg_iovlen = 1; 29./ 设置结束,准备接收30.if (n = recvmsg(fd, &msg, 0) cmsg_len = CMSG_LEN(sizeof( int ) 38. 39./ 还是必要的检查40.if (cmptr

25、-cmsg_level != SOL_SOCKET) 41. 42. printf(control level != SOL_SOCKET/n); 43. exit(-1); 44. 45.if (cmptr-cmsg_type != SCM_RIGHTS) 46. 47. printf(control type != SCM_RIGHTS/n); 48. exit(-1); 49. 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 11 页 - - - - - - - -

26、 - 50./ 好了,描述符在这51. *recvfd = *(int *)CMSG_DATA(cmptr); 52. 53.else54. 55.if (cmptr = NULL) printf(null cmptr, fd not passed./n); 56.else printf(message len%d if incorrect./n, cmptr-cmsg_len); 57. *recvfd = -1; / descriptor was not passed58. 59.#else60.if (msg.msg_accrightslen = sizeof( int ) *recvf

27、d = newfd; 61.else *recvfd = -1; 62.#endif63.return n; 64. 发送和接收函数就这么多, 就像上面看到的,进程间传递套接字还是有点麻烦的。Linux 的就介绍完了,后面在简单说说Windows 是如何传递的,话说MSDN 真的方便哪。原文链接: http:/ Socket )Windows 平台上内核对象都是HANDLE ,如果要在进程间传递内核对象,Windows 提供了 DuplicateHandle函数。复制的 HANDLE 和原 HANDLE 实际上指向的是内核中的同一个对象。对于 Socket 而言,则需要使用 WSADuplic

28、ateSocket来传递 Socket ,这个操作不像 DuplicateHandle那么直观,先来看看函数原型:int WSADuplicateSocket( SOCKET s, / 要复制的 socket 对象DWORD dwProcessId, / 接收进程的进程号LPWSAPROTOCOL_INFO lpProtocolInfo / 把 Socket 的信息复制到这里); 了解这个函数后,就可以简单的步骤。1 首先发送进程要知道接收进程是谁,这个得靠进程间通信了, 比如一块共享内存。2 确认接收进程后,接着发送进程调用WSADuplicateSocket取得一个WSAPROTOCOL_

29、INFO结构,这个结构是复制Socket 的关键,不过不必太关名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 11 页 - - - - - - - - - 心这个结构里面都是什么东西。3 发送进程拿到这个结构后,得把它交给接收进程,拿到WSAPROTOCOL_INFO后,接收进程就可以复制发送进程的Socket 了,这个也可以通过共享内存传递。4 接收进程调用 WSASocket 函数,传递这个接收到的 WSAPROTOCOL_INFO结构就可以复制 Socket 了。

30、需要注意的是,复制的 Socket 并不是一个真正的新的socket , 只是一个复制品,它和原 Socket 共享所有的状态信息。如果原 Socket 是使用 socket 函数创建的, 那么接收进程调用WSASocket 时必须指明 WSA_FLAG_OVERLAPPED标记。MSDN 上介绍的很详细,见下面的时序表格:发送进程IPC 接收进程1) 由 WSASocket , WSAConnect 获取 Socket 2) 请求接收进程 ID 号 (可以通过共享内存取得)= 3) 传递给发送进程ID 号4) 获取接收进程 ID 号 7) 取得 WSAPROTOCOL_INFO结构8) 调用 WSASocket 创建共享 Socket 描述9) 使用 Socket 进行数据通信10) 调用 closesocket关闭 Socket = 从上面的时序表格,很容易就能写出 Windows 上进程间传递 Socket 的函数了。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 11 页 - - - - - - - - -

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 高考资料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁