进程间传递描述符一
来源:互联网 发布:明朝资本主义萌芽知乎 编辑:程序博客网 时间:2024/04/29 00:02
进程间传递描述符一
每个进程都拥有自己独立的进程空间,这使得描述符在进程之间的传递变得有点复杂,这个属于高级进程间通信的内容,下面就来说说。顺便把Linux和Windows平台都讲讲。
Linux下的描述符传递
Linux系统系下,子进程会自动继承父进程已打开的描述符,实际应用中,可能父进程需要向子进程传递“后打开的描述符”,或者子进程需要向父进程传递;或者两个进程可能是无关的,显然这需要一套传递机制。
简单的说,首先需要在这两个进程之间建立一个Unix域套接字接口作为消息传递的通道(Linux系统上使用socketpair函数可以很方面便的建立起传递通道),然后发送进程调用sendmsg向通道发送一个特殊的消息,内核将对这个消息做特殊处理,从而将打开的描述符传递到接收进程。
然后接收方调用recvmsg从通道接收消息,从而得到打开的描述符。然而实际操作起来并不像看起来那样单纯。
先来看几个注意点:
1 需要注意的是传递描述符并不是传递一个int型的描述符编号,而是在接收进程中创建一个新的描述符,并且在内核的文件表中,它与发送进程发送的描述符指向相同的项。
2 在进程之间可以传递任意类型的描述符,比如可以是pipe,open,mkfifo或socket,accept等函数返回的描述符,而不限于套接字。
3 一个描述符在传递过程中(从调用sendmsg发送到调用recvmsg接收),内核会将其标记为“在飞行中”(in flight)。在这段时间内,即使发送方试图关闭该描述符,内核仍会为接收进程保持打开状态。发送描述符会使其引用计数加1。
4 描述符是通过辅助数据发送的(结构体msghdr的msg_control成员),在发送和接收描述符时,总是发送至少1个字节的数据,即使这个数据没有任何实际意义。否则当接收返回0时,接收方将不能区分这意味着“没有数据”(但辅助数据可能有套接字)还是“文件结束符”。
5 具体实现时,msghdr的msg_control缓冲区必须与cmghdr结构对齐,可以看到后面代码的实现使用了一个union结构来保证这一点。
msghdr和cmsghdr结构体
上面说过,描述符是通过结构体msghdr的msg_control成员送的,因此在继续向下进行之前,有必要了解一下msghdr和cmsghdr结构体,先来看看msghdr。
结构成员可以分为下面的四组,这样看起来就清晰多了:
1 套接口地址成员msg_name与 msg_namelen;
只有当通道是数据报套接口时才需要;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成员指向一个 struct iovec数组,iovc结构体在sys/uio.h头文件定义,它没有什么特别的。
有了iovec,就可以使用readv和writev函数在一次函数调用中读取或是写入多个缓冲区,显然比多次read,write更有效率。readv和writev的函数原型如下:
3 附属数据缓冲区成员msg_control与 msg_controllen,描述符就是通过它发送的,后面将会看到,msg_control指向附属数据缓冲区,而msg_controllen指明了缓冲区大小。
4 接收信息标记位msg_flags;忽略
轮到cmsghdr结构了,附属信息可以包括若干个单独的附属数据对象。在每一个对象之前都有一个struct cmsghdr结构。头部之后是填充字节,然后是对象本身。最后,附属数据对象之后,下一个cmsghdr之前也许要有更多的填充字节。
cmsg_len 附属数据的字节数,这包含结构头的尺寸,这个值是由CMSG_LEN()宏计算的;
cmsg_level 表明了原始的协议级别(例如,SOL_SOCKET);
cmsg_type 表明了控制信息类型(例如,SCM_RIGHTS,附属数据对象是文件描述符;SCM_CREDENTIALS,附属数据对象是一个包含证书信息的结构);
被注释的cmsg_data用来指明实际的附属数据的位置,帮助理解。
对于cmsg_level和cmsg_type,当下我们只关心SOL_SOCKET和SCM_RIGHTS。
msghdr和cmsghdr辅助宏
这些结构还是挺复杂的,Linux系统提供了一系列的宏来简化我们的工作,这些宏可以在不同的UNIX平台之间进行移植。这些宏是由cmsg(3)的man手册页描述的,先来认识一下:
#include <sys/socket.h>
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 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()值并不包括可能的结尾填充字符。CMSG_SPACE()宏对于确定所需的缓冲区尺寸是十分有用的。
注意如果在缓冲区中有多个附属数据,一定要同时添加多个CMSG_SPACE()宏调用来得到所需的总空间。
下面的例子反映了二者的区别:
CMSG_DATA()宏
输入参数:指向cmsghdr结构的指针;
返回跟随在头部以及填充字节之后的附属数据的第一个字节(如果存在)的地址,比如传递描述符时,代码将是如下的形式:
CMSG_FIRSTHDR()宏
输入参数:指向structmsghdr结构的指针;
返回指向附属数据缓冲区内的第一个附属对象的struct cmsghdr指针。如果不存在附属数据对象则返回的指针值为NULL。
CMSG_NXTHDR()宏
输入参数:指向structmsghdr结构的指针,指向当前structcmsghdr的指针;
这个用于返回下一个附属数据对象的struct cmsghdr指针,如果没有下一个附属数据对象,这个宏就会返回NULL。
通过这两个宏可以很容易遍历所有的附属数据,像下面的形式:
函数sendmsg和recvmsg
函数原型如下:
二者的参数说明如下:
s,套接字通道,对于sendmsg是发送套接字,对于recvmsg则对应于接收套接字;
msg,信息头结构指针;
flags,可选的标记位,这与send或是sendto函数调用的标记相同。
函数的返回值为实际发送/接收的字节数。否则返回-1表明发生了错误。
具体参考APUE的高级I/O部分,介绍的很详细。
好了准备工作已经做完了,下面就准备进入正题。
- 进程间传递描述符一
- 进程间传递描述符一
- 进程间传递描述符一
- 进程间传递描述符
- 进程间描述符传递
- 进程间传递描述符
- 进程间传递描述符二
- 进程间传递描述符三
- 进程间传递描述符代码参考
- linux进程间传递描述符
- 进程间传递描述符二
- 不相关进程间传递文件描述符
- socketpair 进程间传递描述符
- 进程间传递文件描述符fd
- Linux下进程间传递描述符
- 进程间传递文件描述符
- 进程间传递文件描述符
- 进程间传递文件描述符
- SQL语句中和字符串的拼接问题
- 配置Windows Live Writer日志客户端
- FLEX4实践—DropDownList与ComboBox
- String 的null 与 空
- Oracle内存结构与实例及其管理
- 进程间传递描述符一
- TCP/IP通信程序设计 关于长连接和短连接
- unicode,ansi,utf-8,unicode big endian这些编码有什么区别
- 四次做LFS的心得体会
- Berkeley 5.0.21提供的PHP扩展的Bug
- Java中各种修饰符与访问修饰符的说明
- 2015年,Web 进入移动时代
- CoSign的SSO模型流程图
- 管理的十个寓言故事