linux——进程间共享文件描述符

进程间共享文件描述符主要由三个函数实现:

  1. socketpair():创建一对匿名域套接字;
  2. sendmsg():从套接字一端发送数据;
  3. recvmsg():从套接字另一端接受数据。
linux——进程间共享文件描述符_第1张图片
image.png

1. socketpair()

#include 
#include 
int socketpair(
  int domain,
  int type,
  int protocol,
  int sv[2]
);

参数说明:

  1. domain:表示要建立的套接字的协议族,只能为AF_LOCAL 或AF_UNIX;
  2. type:表示协议,可以是SOCK_STREAM或SOCK_DGRAM。用SOCK_STREAM建立的是管道流,与一般管道的区别是,套接字对建立的通道是双向的,每一端都可以进行读写;
  3. protocol:表示类型,只能为0;
  4. sv[2]:存储建立的套接字对的整数数组。

返回值:
函数调用成功返回0,否则返回-1,并且设置errno。

2. sendmsg()

#include 
ssize_t sendmsg(
  int sockfd,
  const struct msghdr *msg,
  int flags
);

参数说明:

  1. sockfd:文件描述符(sockpair创建的sv[]);
  2. *msg:需要发送的消息的头部结构体,详见下文;
  3. flags:一般置0,其他值与send()相同。

参数*msg涉及到3个结构体:msghdr、iovec和cmasghdr,如下图:

linux——进程间共享文件描述符_第2张图片
image.png

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;
  int msg_flags;
};

成员说明:

  1. *msg_name和msg_namelen表示要发送或接收数据的地址和地址长度,如果是面向连接的,这两个变量可以不用;

  2. *msg_iov用于指定要发送或接收数据的缓冲区地址,可以是数组,如下:

     struct iovec
       {
         void *iov_baes;
         //存放数据的缓冲区
         size_t iov_len;
         //数据长度
       };
    
  3. msg_iovlen表示iovec类型元素的个数;

  4. *msg_control表示控制信息,指向一个cmsghdr结构体, *msg_controllen表示控制信息的长度,详见下文;

  5. msg_flages表示发送和接受消息的标识。

cmsghdr

struct cmsghdr
 {
   socklen_t cmsg_len;
   int cmsg_level;
   int cmsg_type;
   /*unsigned char cmsg_data[]*/
  };

成员说明:

  1. cmsg_len:控制信息的字节计数,包含结构头的数据。这个值是由CMSG_LEN()宏计算的;
  2. cmsg_level:原始的协议级别(例如,SOL_SOCKET);
  3. cmsg_type:控制信息类型(例如,SCM_RIGHTS);
  4. cmsg_data:控制信息的数据部分,实际上这个成员并不存在,数据是直接存储在cmsg_type之后的。

对于这些控制数据的访问,必须使用Linux提供的一些专用宏来完成。这些宏包括如下几个:

#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 CMSG_SPACE(size_t length);

size_t CMSG_LEN(size_t length);

unsigned char *CMSG_DATA(struct cmsghdr *cmsg);

其中:

  1. CMSG_FIRSTHDR()返回msg所指向的msghdr结构体中的第一个cmsghdr结构体指针;
  2. CMSG_NXTHDR()返回传入的cmsghdr类型的指针的下一个cmsghdr结构体的指针;
  3. CMSG_ALIGN()根据传入的length大小,返回一个包含了添加对齐作用的填充数据后的大小;
  4. CMSG_SPACE()中传入的参数length指的是一个控制信息元素(即一个cmsghdr结构体)后面数据部分的字节数,返回的是这个控制信息的总的字节数,即包含了头部(即cmsghdr各成员)、数据部分和填充数据的总和;
  5. CMSG_DATA根据传入的cmsghdr指针参数,返回其后面数据部分的指针;
  6. CMSG_LEN传入的参数是一个控制信息中的数据部分的大小,返回的是这个根据这个数据部分大小,需要配置的cmsghdr结构体中cmsg_len成员的值。这个大小将为对齐添加的填充数据也包含在内。

发送文件描述符的步骤:

  1. 创建一个msghdr结构体,并初始化;
  2. 创建一个cmsghdr结构体,将该结构体的cmsg_level设置为SOL_SOCKET,cmsg_type设置为SCM_RIGHTS,其中,SCM表示socket-level control message,SCM_RIGHTS表示我们要传递访问权限;
  3. 将要传递的文件描述符赋值给cmsghdr的数据部分;
  4. 将该cmsghdr结构体分配给msg_control;
  5. 调用sendmsg()。

具体实现:

static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
//计算cmsg_len的值
/*发送文件描述符 sv参数是用来传递信息的UNIX域socket, 
*fd参数是待发送的文件描述符    */
void send_fd(int sv, int fd)
{
struct msghdr msg;
struct iovec iov[1];

char buf[0];
iov[0].iov_base = buf;
iov[0].iov_len = 1;
//不需要传递其他数据,所以iov数据部分为空
msg.msg_name = NULL;
msg.msg_namelen = 0;
//面向连接,可以不用
msg.msg_iov = iov;
msg.msg_iovlen = 1;

cmsghdr cm;
cm.cmsg_len = CONTROL_LEN;
//用CMSG_LEN()宏获得的值
cm.cmsg_level = SOL_SOCKET;
cm.cmsg_type = SCM_RIGHTS;
 //设置信息类型
*(int*)CMSG_DATA(&cm) = fd;
//将要传递的文件描述符赋值给cmsghdr的数据部分
msg.msg_control = &cm; 
//给msg_control分配设置好的cmsghdr结构体
sendmsg(sv, &msg, 0);
//调用sendmsg()
};

3. recvmsg()

#include 
ssize_t recvmsg(
int sockfd,
const struct msghdr *msg,
int flags
);

接受函数与发送函数大致相同,最后一个参数flags在调用recvmsg之后,会被设置到到msg所指向的msghdr类型的msg_flags变量中。
接收文件描述符的步骤:

  1. 创建一个msghdr结构体,并初始化;
  2. 创建一个cmsghdr结构体,
    将该结构体的cmsg_level设置为SOL_SOCKET,
    cmsg_type设置为SCM_RIGHTS,
    其中,SCM表示socket-level control message,SCM_RIGHTS表示我们要传递访问权限;
  3. 将该cmsghdr结构体分配给msg_control;
  4. 调用recvmsg();
    5.读取cmsghdr的数据部分获得文件描述符。

具体实现:

/*接收文件描述符
  *sv参数是用来传递信息的UNIX域socket */
int recv_fd(int sv)
{
struct msghdr msg;
struct iovec iov[1];

char buf[0];
iov[0].iov_base = buf;
iov[0].iov_len = 1;
//没有其他要接受的数据,iovec数据部分设为空
msg.msg_name = NULL;
msg.msg_namelen = 0;
//面向连接,不需要
msg.msg_iov = iov;
msg.msg_iovlen = 1;

cmsghdr cm;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;
//创建一个cmsghdr结构体,并分配给msg_control

recvmsg(fd, &msg, 0);
//调用recvmsg()

int fd = *(int*)CMSG_DATA(&cm);
//从cmsghdr的数据部分获取文件描述符

return fd
}

4. 主函数

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
int sv[2];
int fd= 0;
/*创建父子进程管道*/
int ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, sv);
assert(ret != -1);
//将创建的管道符存于sv[2]中

pid_t pid = fork();
if(pid == 0)//子进程
{
    close(sv[0]);//关闭管道符一端
    fd = open("test.txt", O_RDWR);
    //  创建一个文件描述符
    send_fd(sv[1], (fd > 0) ? fd: 0);
    //在管道符另一端发送文件描述符
    close(fd_to_pass);
    //关闭文件
    exit(0);
}

//父进程
close(sv[1]);//关闭管道符一端
fd= recv_fd(sv[0]);
//在管道符另一端发送文件描述符
char buf[1024];
memset(buf, '\0', 1024);
read(fd, buf, 1024);
printf("I got fd %d and data %s\n", fd, buf);
//从文件中读取数据
close(fd_to_pass);

return 0;
}

参考网页:

  1. https://blog.csdn.net/y396397735/article/details/51078437
  2. https://blog.csdn.net/wm_1991/article/details/52165666
  3. https://blog.csdn.net/y396397735/article/details/50684558
  4. https://blog.csdn.net/u014209688/article/details/71311973

你可能感兴趣的:(linux——进程间共享文件描述符)