进程间传递描述符

每个进程都拥有自己独立的进程空间,这使得描述符在进程之间的传递变得有点复杂,这个属于高级进程间通信的内容,下面就来说说。

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() 宏

输入参数:指向 struct msghdr 结构的指针;

返回指向附属数据缓冲区内的第一个附属对象的 struct cmsghdr 指针。如果不存在附属数据对象则返回的指针值为 NULL 。

CMSG_NXTHDR() 宏

输入参数:指向 struct msghdr 结构的指针,指向当前 struct cmsghdr 的指针;

这个用于返回下一个附属数据对象的 struct cmsghdr 指针,如果没有下一个附属数据对象,这个宏就会返回 NULL 。

通过这两个宏可以很容易遍历所有的附属数据,像下面的形式:

函数 sendmsg 和 recvmsg

函数原型如下:

二者的参数说明如下:

s, 套接字通道,对于 sendmsg 是发送套接字,对于 recvmsg 则对应于接收套接字;

msg ,信息头结构指针;

flags , 可选的标记位, 这与 send 或是 sendto 函数调用的标记相同。

函数的返回值为实际发送 / 接收的字节数。否则返回 -1 表明发生了错误。

具体参考 APUE 的高级 I/O 部分,介绍的很详细。

好了准备工作已经做完了,下面就准备进入正题。
--------------------- 
作者:sparkliang 
来源:CSDN 
原文:https://blog.csdn.net/sparkliang/article/details/5486069 
版权声明:本文为博主原创文章,转载请附上博文链接!

在大佬博客后面附上从《Linux高性能服务器编程》上抄下来的一个例子:

#include<sys/socket.h>
#include<fcntl.h>
#include<bits/stdc++.h>
#include<unistd.h>
#include<assert.h>
#include<cstring>
using namespace std;
static const int CONTROL_LEN=CMSG_LEN(sizeof(int));
void send_fd(int fd,int fd_to_send)
{
    struct iovec iov[1];
    struct msghdr msg;
    char buf[0];
    iov[0].iov_base=buf;
    iov[0].iov_len=1;
    msg.msg_name=NULL;
    msg.msg_namelen=0;
    msg.msg_iov=iov;
    msg.msg_iovlen=1;

    struct cmsghdr cm;
    cm.cmsg_len=CONTROL_LEN;
    cm.cmsg_level=SOL_SOCKET;
    cm.cmsg_type=SCM_RIGHTS;//SCM_RIGHTS ,附属数据对象是文件描述符;
    *(int*)CMSG_DATA(&cm)=fd_to_send;
    msg.msg_control=&cm;
    msg.msg_controllen=CONTROL_LEN;

    sendmsg( fd,&msg,0);
}
int recv_fd(int fd)
{
    struct iovec iov[1];
    struct msghdr msg;
    char buf[0];

    iov[0].iov_base=buf;
    iov[0].iov_len=1;
    msg.msg_name=NULL;
    msg.msg_namelen=0;
    msg.msg_iov=iov;
    msg.msg_iovlen=1;

    struct cmsghdr cm;
    msg.msg_control=&cm;
    msg.msg_controllen=CONTROL_LEN;

    recvmsg(fd,&msg,0);
    int fd_to_read=*(int*)CMSG_DATA(&cm);
    return fd_to_read;

}
int main()
{
    int pipefd[2];
    int fd_to_pass=0;
    int ret=socketpair(PF_UNIX,SOCK_DGRAM,0,pipefd);
    pid_t pid=fork();
    if(pid==0)
    {
        close(pipefd[0]);
        fd_to_pass=open("test.txt",O_RDWR,0666);
        send_fd(pipefd[1],(fd_to_pass>0)?fd_to_pass:0);
        close(fd_to_pass);
        exit(0);
    }
    close(pipefd[1]);
    fd_to_pass=recv_fd(pipefd[0]);
    char buf[1024];
    memset(buf,0,sizeof(buf));
    read(fd_to_pass,buf,1024);
    printf("i got fd %d and data %s\n",fd_to_pass,buf);
    close(fd_to_pass);

}






运行结果:

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值